Ejemplo n.º 1
0
    private void OnJointBreak(float breakForce)
    {
        FragmentsGraph fg = GetComponentInParent <FragmentsGraph>();

        if (fg != null)
        {
            HingeJoint[] hjs = GetComponents <HingeJoint>();
            if (hjs != null)
            {
                foreach (HingeJoint hj in hjs)
                {
                    if (hj != null && hj.connectedBody != null && hj.connectedBody.gameObject != null)
                    {
                        fg.updateNextFrame.Enqueue(new FragmentsGraph.HingeJointPair(gameObject, hj.connectedBody.gameObject, hj));
                    }
                }
            }
        }
    }
Ejemplo n.º 2
0
    public static IEnumerator Fragment(GameObject toFragmentObject, Stats returnedStats, bool areaInsteadOfSites = false)
    {
        returnedStats.totalTime = 0L;
        Debug.Log("Fragmentation started!");
        Stopwatch stopWatch = new Stopwatch();

        stopWatch.Start();

        // Gather fragmentation properties for the current game object. The program checks the
        // current game object and the topmost game object of the hierarchy for such properties.
        // If none are found, assume default properties.
        FragmentsProperties fragmentsProperties = toFragmentObject.GetComponent <FragmentsProperties>();

        if (fragmentsProperties == null)
        {
            GameObject          root = toFragmentObject.transform.root.gameObject;
            FragmentsProperties rootFragmentsProperties = root.GetComponent <FragmentsProperties>();

            fragmentsProperties = (rootFragmentsProperties == null)
                ? toFragmentObject.AddComponent <FragmentsProperties>()
                : rootFragmentsProperties;
        }

        // Create new game object as parent of future generated fragments. This game object will
        // replace the current game object in the hierarchy and will retain its fragmentation properties.
        GameObject parentObject = new GameObject(toFragmentObject.name + Constants.FragmentStr);

        CopyTransforms(parentObject.transform, toFragmentObject.transform);
        parentObject.transform.SetParent(toFragmentObject.transform.parent, false);

        FragmentsProperties.Copy(parentObject.AddComponent <FragmentsProperties>(), fragmentsProperties);

        // Iterate through the mesh's triangles and divide each triangle in separate Voronoi diagrams.
        // The generated fragments will retain the original mesh's materials, normals and uv coordinates.
        int  totalFragments = 0;
        Mesh mesh           = toFragmentObject.GetComponent <MeshFilter>().mesh;

        for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; ++subMeshIndex)
        {
            int[] triangles = mesh.GetTriangles(subMeshIndex);
            // TODO paralelize
            for (int ti = 0; ti < triangles.Length; ti += 3)
            {
                Vertex[] v = new Vertex[3];
                for (int i = 0; i < 3; ++i)
                {
                    v[i].position = mesh.vertices[triangles[ti + i]];
                    // Assume zero values if missing normal or uv coordinates
                    v[i].normal = (mesh.normals.Length <= triangles[ti + i])
                        ? Vector3.zero
                        : mesh.normals[triangles[ti + i]];
                    v[i].uv = (mesh.uv.Length <= triangles[ti + i])
                        ? Vector2.zero
                        : mesh.uv[triangles[ti + i]];
                }
                List <GameObject> fragments = GenerateVoronoiFragments(v[0], v[1], v[2], fragmentsProperties, areaInsteadOfSites);

                foreach (GameObject fragment in fragments)
                {
                    if (fragment.GetComponent <MeshFilter>() != null)
                    {
                        // Create new game object with fragment's mesh
                        fragment.name = Constants.FrozenFragmentStr + totalFragments;
                        fragment.transform.SetParent(parentObject.transform, false);
                        fragment.tag = Constants.FrozenFragmentStr;

                        MeshRenderer goMeshRenderer       = toFragmentObject.GetComponent <MeshRenderer>();
                        MeshRenderer fragmentMeshRenderer = fragment.AddComponent <MeshRenderer>();
                        int          materialIdx          = subMeshIndex % goMeshRenderer.materials.Length;
                        fragmentMeshRenderer.material = goMeshRenderer.materials[materialIdx];
                        // Hide current fragment's mesh until the destruction of the initial game object
                        // to prevent z-fighting effects.
                        fragmentMeshRenderer.enabled = false;

                        MeshCollider mc = fragment.AddComponent <MeshCollider>();
                        mc.convex = true;
                        Rigidbody rb = fragment.AddComponent <Rigidbody>();
                        rb.detectCollisions = false;
                        rb.isKinematic      = true;
                        rb.interpolation    = RigidbodyInterpolation.Interpolate;
                        rb.mass             = fragmentsProperties.density * FragmentVolume(fragment);
                        rb.angularDrag      = Constants.FragmentRBodyAngDrag;
                        rb.drag             = Constants.FragmentRBodyDrag;

                        fragment.AddComponent <JointBreakListener>();

                        ++totalFragments;
                    }
                    else
                    {
                        // Missing fragment
                        Debug.Log($"Clipped site: {fragment.transform.localPosition}");
                    }
                }

                // Ensure the current function operation does not take a lot of time
                // in a single frame
                if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
                {
                    Debug.Log("Fragmenter Yield in voronoi generation");
                    returnedStats.totalTime += stopWatch.ElapsedMilliseconds;
                    yield return(null);

                    stopWatch.Restart();
                }
            }

            // Ensure the current function operation does not take a lot of time
            // in a single frame
            if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
            {
                Debug.Log("Fragmenter Yield submesh parsing");
                returnedStats.totalTime += stopWatch.ElapsedMilliseconds;
                yield return(null);

                stopWatch.Restart();
            }
        }

        Debug.Log($"Fragmentation Ended at: {returnedStats.totalTime / 1000.0f}");

        // Create an undirected graph with all fragments. Each fragment represents a node in the
        // graph. The edges represent the connectivity between the fragments. Each node will have
        // a direct edge to each of its touching neighbours.
        var graph = new Graph <GameObject, HingeJoint>();

        foreach (Transform child in parentObject.transform)
        {
            if (!child.CompareTag(Constants.FrozenFragmentStr))
            {
                continue;
            }

            GameObject        go = child.gameObject;
            List <GameObject> possibleNeighbours = new List <GameObject>();

            // To find the touching neighbours, do a rough and fast bounding box intersection first.
            foreach (Transform child2 in parentObject.transform)
            {
                GameObject go2 = child2.gameObject;
                if (go != go2)
                {
                    Bounds b1 = go.GetComponent <MeshCollider>().bounds;
                    Bounds b2 = go2.GetComponent <MeshCollider>().bounds;

                    if (b1 == null)
                    {
                        Debug.Log($"{go} does not have a Mesh Collider");
                    }
                    if (b2 == null)
                    {
                        Debug.Log($"{go2} does not have a Mesh Collider");
                    }

                    if (b1 != null && b2 != null && b1.Intersects(b2))
                    {
                        possibleNeighbours.Add(go2);
                    }
                }
            }

            // Do a more fine and more computational heavy intersection. To do so, the mesh collider's
            // size will be slightly increased. The actual mesh collider's size cannot be modified,
            // thus a new game object will be created with a bigger mesh and a mesh collider.
            GameObject biggerGo = new GameObject($"Big_{child.name}");
            biggerGo.transform.SetParent(go.transform);
            CopyTransforms(biggerGo.transform, go.transform);

            biggerGo.AddComponent <MeshFilter>().mesh     = ScaleMesh(go.GetComponent <MeshFilter>().mesh, Constants.MeshUpscaling);
            biggerGo.AddComponent <MeshCollider>().convex = true;

            if (!graph.ContainsNode(go))
            {
                graph.AddNode(go);
            }
            foreach (GameObject neighbour in possibleNeighbours)
            {
                Collider colBigg  = biggerGo.GetComponent <Collider>();
                Collider colNeigh = neighbour.GetComponent <Collider>();

                if (Physics.ComputePenetration(
                        colBigg, go.transform.position, go.transform.rotation,
                        colNeigh, neighbour.transform.position, neighbour.transform.rotation, out _, out _))
                {
                    if (graph.ContainsEdge(go, neighbour) || graph.ContainsEdge(neighbour, go))
                    {
                        continue;
                    }

                    if (!graph.ContainsNode(neighbour))
                    {
                        graph.AddNode(neighbour);
                    }

                    void CreateJoint(GameObject go1, GameObject go2)
                    {
                        Vector3 center1 = FragmentCenter(go1);
                        Vector3 center2 = FragmentCenter(go2);

                        HingeJoint hj = go1.AddComponent <HingeJoint>();

                        hj.connectedBody   = go2.GetComponent <Rigidbody>();
                        hj.enableCollision = false;
                        hj.breakForce      = Constants.FragmentHingeBreakForce;
                        hj.anchor          = center2;
                        hj.axis            = center2 - center1;

                        JointLimits hinjeLimits = hj.limits;

                        hinjeLimits.min               = Constants.FragmentHingeMinAngleDeg;
                        hinjeLimits.bounciness        = 0.0f;
                        hinjeLimits.bounceMinVelocity = 0.0f;
                        hinjeLimits.max               = Constants.FragmentHingeMaxAngleDeg;
                        hj.limits    = hinjeLimits;
                        hj.useLimits = true;

                        graph.AddEdge(go1, go2, hj);
                    }

                    CreateJoint(go, neighbour);
                    CreateJoint(neighbour, go);
                }
            }
            //empty.transform.SetParent(null);
            Object.Destroy(biggerGo);

            // Ensure the current function operation does not take a lot of time
            // in a single frame.
            if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
            {
                Debug.Log("Fragmenter Yield in graph creating");
                returnedStats.totalTime += stopWatch.ElapsedMilliseconds;
                yield return(null);

                stopWatch.Restart();
            }
        }
        //Debug.Log($"Min Area: {minSurface}, Max Area: {maxSurface}");
        FragmentsGraph fragmentsGraph = parentObject.AddComponent <FragmentsGraph>();

        fragmentsGraph.graph = graph;

        // Chose anchor fragments. An anchor fragment is a fragment which is considered crucial in
        // a mesh structural stability. These have to be manually specified. The program decides
        // if a fragment is an anchor if it intersects a 3D object with "Anchor" tag and layer.
        AnchorList anchorList = toFragmentObject.GetComponent <AnchorList>();

        if (anchorList == null)
        {
            anchorList = toFragmentObject.transform.parent.gameObject.GetComponent <AnchorList>();
        }
        if (anchorList != null)
        {
            // Fast and rough intersection
            List <GameObject> possibleAnchored = new List <GameObject>();
            foreach (Transform fragment in parentObject.transform)
            {
                foreach (GameObject anchor in anchorList.gameObjects)
                {
                    Bounds b1 = fragment.GetComponent <Collider>().bounds;
                    Bounds b2 = anchor.GetComponent <BoxCollider>().bounds;
                    if (b1.Intersects(b2))
                    {
                        possibleAnchored.Add(fragment.gameObject);
                    }
                }
            }

            // Slow and accurate intersection
            HashSet <GameObject> anchoredFragments = new HashSet <GameObject>();
            foreach (GameObject fragment in possibleAnchored)
            {
                Collider col1 = fragment.GetComponent <Collider>();
                foreach (GameObject anchor in anchorList.gameObjects)
                {
                    Collider col2 = anchor.GetComponent <Collider>();
                    if (Physics.ComputePenetration(
                            col1, fragment.transform.position, fragment.transform.rotation,
                            col2, anchor.transform.position, anchor.transform.rotation, out _, out _))
                    {
                        anchoredFragments.Add(fragment);
                        //Debug.Log($"Anchored {fragment.name}");
                    }
                }
            }
            fragmentsGraph.anchoredFragments    = anchoredFragments;
            fragmentsGraph.initialAnchoredCount = anchoredFragments.Count;
        }

        foreach (Transform child in parentObject.transform)
        {
            child.gameObject.GetComponent <MeshRenderer>().enabled = true;
            Rigidbody rb = child.gameObject.GetComponent <Rigidbody>();
            rb.isKinematic      = fragmentsGraph.anchoredFragments != null && fragmentsGraph.anchoredFragments.Contains(rb.gameObject);
            rb.detectCollisions = true;
        }

        returnedStats.isDone                 = true;
        returnedStats.fragments              = totalFragments;
        returnedStats.fragmentedGameObject   = parentObject;
        fragmentsGraph.initialFragmentsCount = fragmentsGraph.currentFragmentsCount = totalFragments;

        Object.Destroy(toFragmentObject);
    }
Ejemplo n.º 3
0
    public static IEnumerator Explode(Vector3 hitPoint, float radius, float force, Stats returnedStats, bool immidiateHingeBreak = false)
    {
        Debug.Log("Explosion started!");
        Stopwatch stopWatch = new Stopwatch();

        stopWatch.Start();
        int fragmentsExploded = 0;

        Collider[] colliders = Physics.OverlapSphere(hitPoint, radius);

        HashSet <FragmentsGraph> graphsToFlood = new HashSet <FragmentsGraph>();
        HashSet <FragmentsGraph> graphsVisited = new HashSet <FragmentsGraph>();

        foreach (Collider collider in colliders)
        {
            if (collider.CompareTag(Constants.FrozenFragmentStr))
            {
                FragmentsGraph fragmentsGraph = collider.transform.parent.gameObject.GetComponent <FragmentsGraph>();
                if (fragmentsGraph != null)
                {
                    GameObject[] neighbours = new GameObject[fragmentsGraph.graph.GetNeighbours(collider.gameObject).Count];
                    fragmentsGraph.graph.GetNeighbours(collider.gameObject).Keys.CopyTo(neighbours, 0);

                    // Remove hinges
                    foreach (GameObject neighbour in neighbours)
                    {
                        Object.Destroy(fragmentsGraph.graph.GetEdgeData(collider.gameObject, neighbour));
                        Object.Destroy(fragmentsGraph.graph.GetEdgeData(neighbour, collider.gameObject));

                        fragmentsGraph.graph.RemoveEdge(collider.gameObject, neighbour);
                        fragmentsGraph.graph.RemoveEdge(neighbour, collider.gameObject);
                    }

                    fragmentsGraph.graph.RemoveNode(collider.gameObject);
                    if (fragmentsGraph.anchoredFragments != null)
                    {
                        fragmentsGraph.anchoredFragments.Remove(collider.gameObject);
                    }
                    fragmentsGraph.currentFragmentsCount--;

                    graphsVisited.Add(fragmentsGraph);
                }

                DestroyFragment(collider.gameObject, hitPoint, radius, force);
                ++fragmentsExploded;
            }
            // Apply force to already destroyed fragment.
            else if (collider.CompareTag(Constants.MovingFragmentStr))
            {
                Rigidbody rb = collider.gameObject.GetComponent <Rigidbody>();
                if (rb != null)
                {
                    rb.AddExplosionForce(force, hitPoint, radius);
                }
            }

            // Ensure the current function operation does not take a lot of time
            // in a single frame.
            if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
            {
                Debug.Log("Explosion Yield in parsing colliders");
                returnedStats.totalTime += stopWatch.ElapsedMilliseconds;
                yield return(null);

                stopWatch.Restart();
            }
        }

        bool destroyedAll = false;

        void DestroyAllFragments(FragmentsGraph fg)
        {
            foreach (GameObject fragment in fg.graph.GetNodes())
            {
                foreach (KeyValuePair <GameObject, HingeJoint> pair in fg.graph.GetNeighbours(fragment))
                {
                    Object.Destroy(pair.Value, Random.Range(Constants.FragmentHingeMinDestroyDelay, Constants.FragmentHingeMaxDestroyDelay));
                }

                DestroyFragment(fragment, hitPoint, radius, force, immidiateHingeBreak);
            }
            fg.graph                = null;
            fg.anchoredFragments    = null;
            fg.initialAnchoredCount = 0;
            destroyedAll            = true;
            Debug.Log("Destroyed all fragments");
        }

        foreach (FragmentsGraph fragmentsGraph in graphsVisited)
        {
            if (fragmentsGraph.currentFragmentsCount <= 0.25f * fragmentsGraph.initialFragmentsCount ||
                fragmentsGraph.anchoredFragments == null)
            {
                DestroyAllFragments(fragmentsGraph);
            }
            else
            {
                // Flood fill
                Queue <GameObject>   toVisit = new Queue <GameObject>();
                HashSet <GameObject> visited = new HashSet <GameObject>();

                foreach (GameObject anchoredFragmnt in fragmentsGraph.anchoredFragments)
                {
                    toVisit.Enqueue(anchoredFragmnt);
                }

                while (toVisit.Count > 0)
                {
                    GameObject fragment = toVisit.Dequeue();
                    visited.Add(fragment);

                    if (fragmentsGraph.graph.ContainsNode(fragment))
                    {
                        foreach (GameObject neighbour in fragmentsGraph.graph.GetNeighbours(fragment).Keys)
                        {
                            if (!visited.Contains(neighbour))
                            {
                                toVisit.Enqueue(neighbour);
                            }
                        }
                    }

                    // Ensure the current function operation does not take a lot of time
                    // in a single frame.
                    if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
                    {
                        Debug.Log("Explosion Yield in flood fill");
                        returnedStats.totalTime += stopWatch.ElapsedMilliseconds;
                        yield return(null);

                        stopWatch.Restart();
                    }
                }

                if (visited.Count <= 0.25f * fragmentsGraph.initialFragmentsCount)
                {
                    DestroyAllFragments(fragmentsGraph);
                }
            }
        }

        foreach (FragmentsGraph fragmentsGraph in graphsToFlood)
        {
            // Destroy entire mesh when a certain threshold is exceeded.
            if (fragmentsGraph.anchoredFragments.Count < fragmentsGraph.initialAnchoredCount / 2)
            {
                foreach (GameObject fragment in fragmentsGraph.graph.GetNodes())
                {
                    //fragmentsGraph.anchoredFragments.Remove(fragment);
                    DestroyFragment(fragment, hitPoint, radius, force);

                    // Ensure the current function operation does not take a lot of time
                    // in a single frame.
                    if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
                    {
                        Debug.Log("Explosion Yield in destroying entire mesh");
                        yield return(null);

                        stopWatch.Restart();
                    }
                }
                fragmentsGraph.graph                = null;
                fragmentsGraph.anchoredFragments    = null;
                fragmentsGraph.initialAnchoredCount = 0;
            }
            else
            {
                // Use flood fill to detect isolated islands of fragments.
                Queue <GameObject>   toVisit = new Queue <GameObject>();
                HashSet <GameObject> visited = new HashSet <GameObject>();

                foreach (GameObject anchoredFragmnt in fragmentsGraph.anchoredFragments)
                {
                    toVisit.Enqueue(anchoredFragmnt);
                }

                while (toVisit.Count > 0)
                {
                    GameObject fragment = toVisit.Dequeue();
                    visited.Add(fragment);

                    if (fragmentsGraph.graph.ContainsNode(fragment))
                    {
                        foreach (GameObject neighbour in fragmentsGraph.graph.GetNeighbours(fragment).Keys)
                        {
                            if (!visited.Contains(neighbour))
                            {
                                toVisit.Enqueue(neighbour);
                            }
                        }
                    }

                    // Ensure the current function operation does not take a lot of time
                    // in a single frame.
                    if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
                    {
                        Debug.Log("Explosion Yield in flood fill");
                        yield return(null);

                        stopWatch.Restart();
                    }
                }

                // Find isolated islands of fragments and destroy all fragments from them.
                HashSet <GameObject> toRemove = new HashSet <GameObject>();

                foreach (GameObject fragment in fragmentsGraph.graph.GetNodes())
                {
                    if (!visited.Contains(fragment))
                    {
                        toRemove.Add(fragment);
                    }

                    // Ensure the current function operation does not take a lot of time
                    // in a single frame.
                    if (stopWatch.ElapsedMilliseconds > Constants.MaxTimeMs)
                    {
                        Debug.Log("Explosion Yield in finding islands");
                        yield return(null);

                        stopWatch.Restart();
                    }
                }

                foreach (GameObject fragment in toRemove)
                {
                    fragmentsGraph.graph.RemoveNode(fragment);
                    fragmentsGraph.anchoredFragments.Remove(fragment);
                    DestroyFragment(fragment, hitPoint, radius, force);
                }
            }
        }

        if (returnedStats != null)
        {
            returnedStats.isDone               = true;
            returnedStats.fragments            = fragmentsExploded;
            returnedStats.fragmentedGameObject = null;
            returnedStats.destroyedAll         = destroyedAll;
        }
    }