예제 #1
0
 public static void SetDefaults(FragmentsProperties fp)
 {
     fp.minThickness     = Constants.DefaultMinThickness;
     fp.maxThickness     = Constants.DefaultMaxThickness;
     fp.sitesPerTriangle = Constants.DefaultSites;
     fp.maxArea          = Constants.DefaultMaxArea;
     fp.density          = Constants.DefaultDensity;
 }
예제 #2
0
 public static void Copy(FragmentsProperties dst, FragmentsProperties src)
 {
     dst.minThickness     = src.minThickness;
     dst.maxThickness     = src.maxThickness;
     dst.sitesPerTriangle = src.sitesPerTriangle;
     dst.maxArea          = src.maxArea;
     dst.density          = src.density;
     dst.desnities        = new Dictionary <string, float>(src.desnities);
 }
    // Start is called before the first frame update
    void Awake()
    {
        // Load values from menu or assume defaults
        if (PlayerPrefs.HasKey("HouseNumber") == false)
        {
            PlayerPrefs.SetInt("HouseNumber", 9);
            housesNum = 9;
        }
        else
        {
            housesNum = PlayerPrefs.GetInt("HouseNumber");
        }

        if (PlayerPrefs.HasKey("GrowTrees") == false)
        {
            PlayerPrefs.SetInt("GrowTrees", 1);
            treeGrowing = true;
        }
        else
        {
            treeGrowing = System.Convert.ToBoolean(PlayerPrefs.GetInt("GrowTrees"));
        }

        if (PlayerPrefs.HasKey("IvyGrowingSpeed") == false)
        {
            PlayerPrefs.SetInt("IvyGrowingSpeed", (int)IvyGrowingSpeed.medium);
            ivyGrowingSpeed = IvyGrowingSpeed.medium;
        }
        else
        {
            ivyGrowingSpeed = (IvyGrowingSpeed)PlayerPrefs.GetInt("IvyGrowingSpeed");
        }


        if (!PlayerPrefs.HasKey("SitesPerTriangle"))
        {
            PlayerPrefs.SetInt("SitesPerTriangle", 3);
            sitesPerTriangle = 3;
        }
        else
        {
            sitesPerTriangle = PlayerPrefs.GetInt("SitesPerTriangle");
        }

        if (!PlayerPrefs.HasKey("DestructionStart"))
        {
            PlayerPrefs.SetInt("DestructionStart", 0);
            destructionStart = DestructionTime.soon;
        }
        else
        {
            destructionStart = (DestructionTime)PlayerPrefs.GetInt("DestructionStart");
        }

        // Change number of houses in the scene
        housesNum = Mathf.Clamp(housesNum, 0, houses.Count);
        for (int i = houses.Count - 1; i >= housesNum; --i)
        {
            DestroyImmediate(houses[houses.Count - 1]);
            houses.RemoveAt(houses.Count - 1);
        }

        foreach (GameObject house in houses)
        {
            //house.transform.Find("Walls+Roof").gameObject.GetComponent<FragmentsProperties>().sitesPerTriangle = sitesPerTriangle;
            foreach (Transform child in house.transform)
            {
                if (child.name.StartsWith("House"))
                {
                    Transform grandchild = child.Find("Walls+Roof");
                    if (grandchild != null)
                    {
                        FragmentsProperties fp = grandchild.gameObject.GetComponent <FragmentsProperties>();
                        if (fp != null)
                        {
                            fp.sitesPerTriangle = sitesPerTriangle;
                        }
                    }
                }
            }
        }

        // Change how fast the ivy will grow in the scene
        foreach (IvyBehavior iv in FindObjectsOfType <IvyBehavior>())
        {
            switch (ivyGrowingSpeed)
            {
            case IvyGrowingSpeed.none:
                iv.refreshInterval = 1000.0f;
                iv.probability     = 0.0f;
                break;

            case IvyGrowingSpeed.slow:
                iv.refreshInterval = 0.7f;
                iv.probability     = 0.5f;
                break;

            case IvyGrowingSpeed.medium:
                iv.refreshInterval = 0.5f;
                iv.probability     = 0.5f;
                break;

            case IvyGrowingSpeed.fast:
                iv.refreshInterval = 0.3f;
                iv.probability     = 0.5f;
                break;

            case IvyGrowingSpeed.veryFast:
                iv.refreshInterval = 0.1f;
                iv.probability     = 0.7f;
                break;
            }
        }

        // Change whether tree will grow in the scene
        foreach (TreeGrowing t in FindObjectsOfType <TreeGrowing>())
        {
            t.isGrowing = treeGrowing;
        }

        switch (destructionStart)
        {
        case DestructionTime.soon:
            Constants.DestroyerMiscMinStart = 5.0f;
            Constants.DestroyerMiscMaxStart = 6.0f;

            Constants.DestroyerBuildMinStart = 10.0f;
            Constants.DestroyerBuildMaxStart = 15.0f;
            break;

        case DestructionTime.late:
            Constants.DestroyerMiscMinStart = 10.0f;
            Constants.DestroyerMiscMaxStart = 15.0f;

            Constants.DestroyerBuildMinStart = 40.0f;
            Constants.DestroyerBuildMaxStart = 50.0f;
            break;
        }
    }
예제 #4
0
    /// <summary>
    /// Given a triangle, subdivide it in multiple polygons using Voronoi diagrams.
    /// </summary>
    /// <param name="v0">First vertex of the triangle.</param>
    /// <param name="v1">Second vertex of the triangle.</param>
    /// <param name="v2">Third vertex of the triangle.</param>
    /// <param name="fragmentsProperties">Properties of the resulted fragments.</param>
    /// <returns>A list with all generated fragments' meshes. If the function failes to generate
    /// a fragment, it will the site of the fragment as a placeholder.</returns>
    private static List <GameObject> GenerateVoronoiFragments(
        Vertex v0, Vertex v1, Vertex v2, FragmentsProperties fragmentsProperties, bool areaInsteadOfSites = false)
    {
        if (fragmentsProperties == null)
        {
            throw new System.ArgumentNullException("fragmentsProperties");
        }

        float area = 0.5f * Vector3.Cross(v0.position - v1.position, v0.position - v2.position).magnitude;
        //Debug.Log($"Area {area}");
        //Debug.Log($"Max Area: {fragmentsProperties.maxArea}");

        // The Voronoi API suffers from innacurate floating-point approximations and provides erroneous
        // results when dealing with small numbers. A current solution to this problem is to upscale
        // the whole mesh, do the calculations and then downscale the result to the original scale.
        Vector3 scale = new Vector3(
            Constants.VoronoiScale, Constants.VoronoiScale, Constants.VoronoiScale
            );
        Vector3 reverseScale = new Vector3(
            1.0f / Constants.VoronoiScale, 1.0f / Constants.VoronoiScale, 1.0f / Constants.VoronoiScale
            );

        v0.position.Scale(scale);
        v1.position.Scale(scale);
        v2.position.Scale(scale);

        // The Voronoi API requires 2D points while the trianlge is in 3D space. In order to reduce
        // one dimension, all points must have the same z coordinate. Thus, rotate the triangle such
        // that its normal will become (0, 0, 1).
        Vector3    center          = (v0.position + v1.position + v2.position) / 3.0f;
        Vector3    triangleNorm    = Vector3.Cross(v1.position - v0.position, v2.position - v0.position).normalized;
        Quaternion rotation        = Quaternion.FromToRotation(triangleNorm, Vector3.forward);
        Quaternion reverseRotation = Quaternion.FromToRotation(Vector3.forward, triangleNorm);
        Vector3    p0rot           = (rotation * (v0.position - center) + center);
        Vector3    p1rot           = (rotation * (v1.position - center) + center);
        Vector3    p2rot           = (rotation * (v2.position - center) + center);
        float      z = p0rot.z;

        // Try to make all fragments have similar area
        int sitesNum = areaInsteadOfSites ? Math.Max(3, (int)(area / fragmentsProperties.maxArea)) : fragmentsProperties.sitesPerTriangle;//Math.Max(3, (int)(area / fragmentsProperties.maxArea));

        //Debug.Log($"Sites num: {sitesNum}");
        Vector2[] sites = new Vector2[sitesNum];

        // Generate random points inside the triangle. These will be the sites passed to the Voronoi API.
        for (int i = 0; i < sites.Length; ++i)
        {
            Vector3 p         = Vector3.zero;
            int     triesLeft = 50;
            while (triesLeft > 0)
            {
                float r1 = Random.Range(0.1f, 0.9f);
                float r2 = Random.Range(0.1f, 0.9f);

                p = (float)(1.0f - Math.Sqrt(r1)) * p0rot +
                    (float)(Math.Sqrt(r1) * (1.0f - r2)) * p1rot +
                    (float)(r2 * Math.Sqrt(r1)) * p2rot;
                if (PointInTriangle(p0rot, p1rot, p2rot, p))
                {
                    break;
                }
                Debug.Log($"Bad point {p:F5}\n" +
                          $" inside triangle, ({p0rot:F5}, {p1rot:F5}, {p2rot:F5})\n" +
                          $"{triesLeft} tries left");
                --triesLeft;
            }
            sites[i] = p;
        }

        // Calculate the Voronoi diagram containing the given sites
        VoronoiCalculator calc    = new VoronoiCalculator();
        VoronoiClipper    clip    = new VoronoiClipper();
        VoronoiDiagram    diagram = calc.CalculateDiagram(sites);

        Vector2[] triangleClipper = new Vector2[3]
        {
            new Vector2(p0rot.x, p0rot.y),
            new Vector2(p1rot.x, p1rot.y),
            new Vector2(p2rot.x, p2rot.y)
        };
        List <Vector2>    clipped   = new List <Vector2>();
        List <GameObject> fragments = new List <GameObject>(sites.Length);

        // Generate a mesh for each site's polygon
        for (int i = 0; i < sites.Length; ++i)
        {
            clipped.Clear();
            clip.ClipSite(diagram, triangleClipper, i, ref clipped);

            if (clipped.Count > 0)
            {
                // Rotate the points back to their original rotation
                Vector3[] originalSpacePoints = new Vector3[clipped.Count];
                for (int j = 0; j < clipped.Count; ++j)
                {
                    Vector3 v = new Vector3(clipped[j].x, clipped[j].y, z);
                    originalSpacePoints[j] = reverseRotation * (v - center) + center;
                }

                Vertex[] vertices = CalculateNormalsAndUVs(v0, v1, v2, originalSpacePoints);

                // Revert the upscaling and scale to original object scale
                for (int j = 0; j < clipped.Count; ++j)
                {
                    vertices[j].position.Scale(reverseScale);
                }

                float thickness = Random.Range(fragmentsProperties.minThickness, fragmentsProperties.maxThickness);
                fragments.Add(PolygonToFrustum(vertices, thickness));
            }
        }
        return(fragments);
    }
예제 #5
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);
    }