Beispiel #1
0
    public static void SetSpawnTransform(Transform car, float dist, int height)
    {
        int point = 0;

        dist = Mathf.Repeat(dist, Instance.TotalLength);

        while (Instance._distances[point] < dist)
        {
            ++point;
        }

        if (dist == 0)
        {
            ++point;
        }

        Instance.calculatePandM(--point);

        float t = Mathf.InverseLerp(Instance._distances[point], Instance._distances[point + 1], dist);

        Vector3 tangent;

        float percentThrough = t;

        Vector3 position = CatmullRom.Interpolate(Instance.p0, Instance.p1, Instance.m0, Instance.m1, t, out tangent);

        Vector3 normal = Vector3.Lerp(Instance._points[point].Up, Instance._points[(point + 1) % Instance._points.Capacity].Up, percentThrough);
        float   center = Mathf.Lerp(Instance._points[point].Centre, Instance._points[(point + 1) % Instance._points.Capacity].Centre, percentThrough);
        float   width  = Mathf.Lerp(Instance._points[point].Width, Instance._points[(point + 1) % Instance._points.Capacity].Width, percentThrough);

        car.position = position + Vector3.Cross(tangent, normal).normalized *width *center + height * Vector3.up;
        car.up       = normal;
        car.forward  = tangent;
    }
    private void ConstructMesh()
    {
        trackMesh.Clear();
        List <Vector2> texCoords = new List <Vector2> ();
        List <int>     indices   = new List <int> ();

        meshCoords = new List <Vector3> ();
        CatmullRom CRS            = new CatmullRom(diffCoords);
        int        diffCoordsSize = CRS.pts.Count;
        float      step           = 1.0f / (diffCoordsSize * smoothness);
        float      iterator       = 0.0f;

        List <Vector3> outerCoords = new List <Vector3> ();
        List <Vector3> innerCoords = new List <Vector3> ();

        for (int i = 0; i < diffCoordsSize * smoothness; i++)
        {
            outerCoords.Add(CRS.DisplaceBy(trackWidth, iterator, step));
            innerCoords.Add(CRS.Interpolate(iterator));
            iterator += step;
        }

        WeldOverlappingSections(outerCoords, innerCoords);

        for (int i = 0; i < diffCoordsSize * smoothness; i++)
        {
            meshCoords.Add(innerCoords [i]);
            meshCoords.Add(outerCoords [i]);
            meshCoords.Add(innerCoords [i]);
            meshCoords.Add(outerCoords [i]);

            texCoords.Add(new Vector2(0, 0));
            texCoords.Add(new Vector2(1, 0));
            texCoords.Add(new Vector2(0, 1));
            texCoords.Add(new Vector2(1, 1));
        }

        int meshCount = meshCoords.Count;

        for (int i = 0; i < meshCount; i++)
        {
            // Tri 1
            indices.Add(i % meshCount);
            indices.Add((i + 2) % meshCount);
            indices.Add((i + 1) % meshCount);
            // Tri 2
            indices.Add((i + 2) % meshCount);
            indices.Add((i + 3) % meshCount);
            indices.Add((i + 1) % meshCount);
        }

        trackMesh.vertices = meshCoords.ToArray();
        trackMesh.uv       = texCoords.ToArray();
        trackMesh.RecalculateNormals();
        trackMesh.Optimize();
        trackMesh.SetIndices(indices.ToArray(), MeshTopology.LineStrip, 0);
    }
Beispiel #3
0
    // Basically the same as UpdateTrack() just drawing pretty balls and stuff...
    void OnDrawGizmos()
    {
        int closedAdjustment = ClosedLoop ? 0 : 1;

        float currentDistance = 0.0f;
        float step            = TotalLength / CurveResolution;

        int ind = 0;

        //// First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on
        for (int i = 0; i < TrackManager.Track.Points.Count - closedAdjustment; i++)
        {
            calculatePandM(i);

            Vector3 position;

            float t;

            // Second for loop actually creates the spline for this particular segment
            while (currentDistance < _distances[i + 1])
            {
                t = Mathf.InverseLerp(_distances[i], _distances[i + 1], currentDistance);

                Vector3 tangent;

                float percentThrough = t;

                position = CatmullRom.Interpolate(p0, p1, m0, m1, t, out tangent);

                Gizmos.color = Color.red;
                Gizmos.DrawSphere(position, 0.25f);

                Vector3 normal;
                float   centre;
                float   width;
                CalculateNormalCenterWidth(i, percentThrough, out normal, out centre, out width);

                Gizmos.color = Color.green;
                Gizmos.DrawSphere(position + Vector3.Cross(tangent, normal).normalized *width *(1 + centre), 0.5f);
                Gizmos.DrawSphere(position - Vector3.Cross(tangent, normal).normalized *width *(1 - centre), 0.5f);

                Gizmos.color = Color.yellow;
                Gizmos.DrawSphere(position + Vector3.Cross(tangent, normal).normalized *width *centre, 0.8f);

                ++ind;
                currentDistance += step;
            }
        }
    }
Beispiel #4
0
    //TODO:: this function doesn't do well on slopes or loops. Make it look at the normal.
    // Called for the entire track to generate all the tokens for the track. (randomly) Self explanitory...
    public static void SetTokenPositions(int numTokens)
    {
        int closedAdjustment = Instance.ClosedLoop ? 0 : 1;

        float currentDistance = 0.0f;
        float step            = Instance.TotalLength / numTokens;

        int ind = 0;

        // First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on
        for (int i = 0; i < TrackManager.Track.Points.Count - closedAdjustment; i++)
        {
            Instance.calculatePandM(i);

            Vector3 position;

            float t;

            while (currentDistance < Instance._distances[i + 1])
            {
                t = Mathf.InverseLerp(Instance._distances[i], Instance._distances[i + 1], currentDistance);

                Vector3 tangent;

                float percentThrough = t;

                position = CatmullRom.Interpolate(Instance.p0, Instance.p1, Instance.m0, Instance.m1, t, out tangent);

                Vector3 normal;
                float   centre;
                float   width;
                Instance.CalculateNormalCenterWidth(i, percentThrough, out normal, out centre, out width);

                float centerOffset = Random.Range(-1.0f, 1.0f);

                TokenSpawner.SpawnTokensAtPoint(2 * normal + position + Vector3.Cross(tangent, normal).normalized *width *(centre + centerOffset));

                ++ind;
                currentDistance += step;
            }
        }
    }
Beispiel #5
0
    public void MoveFeetCatmull(AvatarIKGoal goal, Vector3 currentPosition, float speed)
    {
        var step = speed * Time.deltaTime;
        //to add parameters from function call
        var newLocalPos = catmullRomSpline.Interpolate(localFootProjection, 0, 0.5f);

        newGlobalPosition = matrix.MultiplyPoint3x4(new Vector3(0, newLocalPos.y, localFootProjection.z));
        // newGlobalPosition = matrix.MultiplyPoint3x4(newLocalPos);

        var yVar = Mathf.MoveTowards(currentPosition.y, newGlobalPosition.y, step);

        newGlobalPosition.y = yVar;
        _animator.SetIKPositionWeight(goal, 1);
        _animator.SetIKPosition(goal, newGlobalPosition);

        // var test = new Vector3(0, newGlobalPosition.y, currentPosition.z);
        // var newY = Vector3.MoveTowards(currentPosition, test, step);
        // test.y = newY.y;
        // _animator.SetIKPositionWeight(goal, 1);
        // _animator.SetIKPosition(goal, test);
    }
Beispiel #6
0
    // TODO:: simplify this function.
    // Set where the vehicle will spawn, according to how far it has traveled through the track.
    public static void SetSpawnTransform(Transform car, float dist, int height)
    {
        int point = 0;

        //Keep the distance in the correct range. Even if the player has done multiple laps.
        dist = Mathf.Repeat(dist, Instance.TotalLength);

        // go through distances until at correct segment index. Works, and is simple. ;)
        while (Instance._distances[point] < dist)
        {
            ++point;
        }

        if (dist == 0)
        {
            ++point;
        }

        Instance.calculatePandM(--point);

        float t = Mathf.InverseLerp(Instance._distances[point], Instance._distances[point + 1], dist);

        Vector3 tangent;

        float percentThrough = t;

        Vector3 position = CatmullRom.Interpolate(Instance.p0, Instance.p1, Instance.m0, Instance.m1, t, out tangent);

        Vector3 normal;
        float   centre;
        float   width;

        Instance.CalculateNormalCenterWidth(point, percentThrough, out normal, out centre, out width);

        car.position = position + Vector3.Cross(tangent, normal).normalized *width *centre + height * Vector3.up;
        car.up       = normal;
        car.forward  = tangent;
    }
Beispiel #7
0
    void UpdateTrack()
    {
        MeshFilter mf   = GetComponent <MeshFilter>();
        Mesh       mesh = mf.sharedMesh;

        int closedAdjustment = ClosedLoop ? 0 : 1;

        int pointsToMake = CurveResolution + 1;

        Vector3[] vertices = new Vector3[pointsToMake * 2 + 2];

        int[] triangles = new int[pointsToMake * 6];

        float currentDistance = 0.0f;
        float step            = TotalLength / CurveResolution;

        int ind = 0;

        // First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on
        for (int i = 0; i < _points.Count - closedAdjustment; i++)
        {
            calculatePandM(i);

            Vector3 position;

            float t;

            // Second for loop actually creates the spline for this particular segment
            while (currentDistance <= _distances[i + 1])
            {
                t = Mathf.InverseLerp(_distances[i], _distances[i + 1], currentDistance);

                Vector3 tangent;

                float percentThrough = t;

                position = CatmullRom.Interpolate(p0, p1, m0, m1, t, out tangent);

                Vector3 normal = Vector3.Lerp(_points[i].Up, _points[(i + 1) % _points.Capacity].Up, percentThrough);
                float   center = Mathf.Lerp(_points[i].Centre, _points[(i + 1) % _points.Capacity].Centre, percentThrough);
                float   width  = Mathf.Lerp(_points[i].Width, _points[(i + 1) % _points.Capacity].Width, percentThrough);

                vertices[ind * 2]     = position + Vector3.Cross(tangent, normal).normalized *width *(1 + center);
                vertices[ind * 2 + 1] = position - Vector3.Cross(tangent, normal).normalized *width *(1 - center);

                triangles[ind * 6]     = ind * 2 + 1;
                triangles[ind * 6 + 1] = ind * 2;
                triangles[ind * 6 + 2] = ind * 2 + 2;
                triangles[ind * 6 + 3] = ind * 2 + 1;
                triangles[ind * 6 + 4] = ind * 2 + 2;
                triangles[ind * 6 + 5] = ind * 2 + 3;

                ++ind;
                currentDistance += step;
            }
        }

        vertices[pointsToMake * 2]     = vertices[0];
        vertices[pointsToMake * 2 + 1] = vertices[1];

        mesh.Clear();
        mesh.vertices = vertices;

        mesh.triangles = triangles;

        mesh.Optimize();

        mesh.RecalculateNormals();
        mesh.RecalculateBounds();

        MeshCollider MCollider = GetComponent <MeshCollider>();

        MCollider.sharedMesh = mesh;
    }
Beispiel #8
0
    public static List <Vector3> MakeBetterCurve(List <Vector3> points, int resolution, bool loop)
    {
        List <Vector3> geometry    = loop? points.GetRange(0, points.Count - 1) : points.GetRange(0, points.Count);
        List <Vector3> betterCurve = new List <Vector3> ();

        //Number of segments
        int segmentsCount = loop ? (resolution) * (geometry.Count) : (resolution) * (geometry.Count - 1);

        if (segmentsCount <= 1)         //If the count is less than 2 points it's not a line, return.
        {
            return(betterCurve);
        }

        Vector3 p0;
        Vector3 p1;
        Vector3 m0;
        Vector3 m1;


        int closedAdjustment = loop ? 0 : 1;

        for (int i = 0; i < geometry.Count - closedAdjustment; i++)
        {
            p0 = geometry[i];
            p1 = (loop == true && i == geometry.Count - 1) ? geometry[0] : geometry[i + 1];

            // m0
            if (i == 0)
            {
                m0 = loop ? 0.5f * (p1 - geometry[geometry.Count - 1]) : p1 - p0;
            }
            else
            {
                m0 = 0.5f * (p1 - geometry[i - 1]);
            }

            // m1
            if (loop)
            {
                if (i == 0)
                {
                    m1 = 0.5f * (geometry[i + 2] - p0);
                }
                else
                {
                    m1 = 0.5f * (geometry[(i + 2) % geometry.Count] - p0);
                }
            }
            else
            {
                if (i < geometry.Count - 2)
                {
                    m1 = 0.5f * (geometry[(i + 2) % geometry.Count] - p0);
                }
                else
                {
                    m1 = p1 - p0;
                }
            }

            float t;
            float pointStep = 1.0f / resolution;
            if ((i == geometry.Count - 2 && loop == false) || (i == geometry.Count - 1 && loop))
            {
                pointStep = 1.0f / (resolution - 1);                 // last point of last segment should reach p1
            }

            //Create Road segments
            for (int j = 0; j < resolution; j++)
            {
                t = j * pointStep;
                betterCurve.Add(CatmullRom.Interpolate(p0, p1, m0, m1, t));
            }
        }

        return(betterCurve);
    }
Beispiel #9
0
    // Fill the mesh with the correct vertices according to the path.
    void UpdateTrack()
    {
        MeshFilter mf   = GetComponent <MeshFilter>();
        Mesh       mesh = mf.sharedMesh;

        int closedAdjustment = ClosedLoop ? 0 : 1;

        int pointsToMake = CurveResolution + 1;

        Vector3[] vertices = new Vector3[pointsToMake * 2 + 2];

        int[] triangles = new int[pointsToMake * 6];

        float currentDistance = 0.0f;
        float step            = TotalLength / CurveResolution;

        int ind = 0;

        // First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on
        for (int i = 0; i < TrackManager.Track.Points.Count - closedAdjustment; i++)
        {
            calculatePandM(i);

            Vector3 position;

            float t;

            // Second for loop actually creates the spline for this particular segment
            while (currentDistance <= _distances[i + 1])
            {
                t = Mathf.InverseLerp(_distances[i], _distances[i + 1], currentDistance);

                Vector3 tangent;

                float percentThrough = t;

                position = CatmullRom.Interpolate(p0, p1, m0, m1, t, out tangent);

                // Debug.Log("Track details:: Num points: " + TrackManager.Track.Points.Count + "; i is: " + i + "; capacity: " + TrackManager.Track.Points.Count);
                Vector3 normal;
                float   centre;
                float   width;
                CalculateNormalCenterWidth(i, percentThrough, out normal, out centre, out width);

                vertices[ind * 2]     = position + Vector3.Cross(tangent, normal).normalized *width *(1 + centre);
                vertices[ind * 2 + 1] = position - Vector3.Cross(tangent, normal).normalized *width *(1 - centre);

                triangles[ind * 6]     = ind * 2 + 1;
                triangles[ind * 6 + 1] = ind * 2;
                triangles[ind * 6 + 2] = ind * 2 + 2;
                triangles[ind * 6 + 3] = ind * 2 + 1;
                triangles[ind * 6 + 4] = ind * 2 + 2;
                triangles[ind * 6 + 5] = ind * 2 + 3;

                ++ind;
                currentDistance += step;
            }
        }

        // close the loop, so the track is complete. (NOTE:: there seems to be a subtle bug with this that I haven't had the patience to fix).
        vertices[pointsToMake * 2]     = vertices[0];
        vertices[pointsToMake * 2 + 1] = vertices[1];

        // populate and sort out the mesh. (the following +/- 10 lines)
        mesh.Clear();
        mesh.vertices = vertices;

        mesh.triangles = triangles;

        mesh.Optimize();

        mesh.RecalculateNormals();
        mesh.RecalculateBounds();

        MeshCollider MCollider = GetComponent <MeshCollider>();

        MCollider.sharedMesh = mesh;
    }
    public void DrawSpline(bool store)
    {
        if (store)
        {
            points.Clear();
            inner.Clear();
            outer.Clear();

            if (generateWaypoints)
            {
                waypoints.Clear();
            }
        }

        Vector3 p0;
        Vector3 p1;
        Vector3 m0;
        Vector3 m1;

        int pointsToMake;

        if (ClosedLoop == true)
        {
            pointsToMake = (CurveResolution) * (ControlPoints.Count);
        }
        else
        {
            pointsToMake = (CurveResolution) * (ControlPoints.Count - 1);
        }

        if (pointsToMake > 0) //Prevent Number Overflow
        {
            CurveCoordinates = new Vector3[pointsToMake];
            Tangents         = new Vector3[pointsToMake];

            int closedAdjustment = ClosedLoop ? 0 : 1;

            // First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on
            for (int i = 0; i < ControlPoints.Count - closedAdjustment; i++)
            {
                p0 = ControlPoints[i].transform.position;
                p1 = (ClosedLoop == true && i == ControlPoints.Count - 1) ? ControlPoints[0].transform.position : ControlPoints[i + 1].transform.position;

                // Tangent calculation for each control point
                // Tangent M[k] = (P[k+1] - P[k-1]) / 2
                // With [] indicating subscript

                // m0
                if (i == 0)
                {
                    m0 = ClosedLoop ? 0.5f * (p1 - ControlPoints[ControlPoints.Count - 1].transform.position) : p1 - p0;
                }
                else
                {
                    m0 = 0.5f * (p1 - ControlPoints[i - 1].transform.position);
                }

                // m1
                if (ClosedLoop)
                {
                    if (i == ControlPoints.Count - 1)
                    {
                        m1 = 0.5f * (ControlPoints[(i + 2) % ControlPoints.Count].transform.position - p0);
                    }
                    else if (i == 0)
                    {
                        m1 = 0.5f * (ControlPoints[i + 2].transform.position - p0);
                    }
                    else
                    {
                        m1 = 0.5f * (ControlPoints[(i + 2) % ControlPoints.Count].transform.position - p0);
                    }
                }
                else
                {
                    if (i < ControlPoints.Count - 2)
                    {
                        m1 = 0.5f * (ControlPoints[(i + 2) % ControlPoints.Count].transform.position - p0);
                    }
                    else
                    {
                        m1 = p1 - p0;
                    }
                }

                Vector3 position;
                float   t;
                float   pointStep = 1.0f / CurveResolution;

                if ((i == ControlPoints.Count - 2 && ClosedLoop == false) || (i == ControlPoints.Count - 1 && ClosedLoop))
                {
                    pointStep = 1.0f / (CurveResolution - 1);
                    // last point of last segment should reach p1
                }
                // Second for loop actually creates the spline for this particular segment
                for (int j = 0; j < CurveResolution; j++)
                {
                    t = j * pointStep;
                    Vector3 tangent;
                    position = CatmullRom.Interpolate(p0, p1, m0, m1, t, out tangent);
                    CurveCoordinates[i * CurveResolution + j] = position;
                    Tangents[i * CurveResolution + j]         = tangent;

                    if (debug) //Normals
                    {
                        Debug.DrawLine(position + Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2 + transform.position, position - Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2 + transform.position, Color.red);
                        Debug.DrawLine(position + Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2 + transform.position, position + Vector3.Cross(tangent, Vector3.up).normalized *(extrude / 2 + edgeWidth) + transform.position, Color.green);   //Edge +
                        Debug.DrawLine(position - Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2 + transform.position, position - Vector3.Cross(tangent, Vector3.up).normalized *(extrude / 2 + edgeWidth) + transform.position, Color.green);   //Edge -
                    }

                    if (store)
                    {
                        points.Add(position - Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2);
                        points.Add(position);
                        points.Add(position + Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2);

                        inner.Add(position - Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2);
                        inner.Add(position - Vector3.Cross(tangent, Vector3.up).normalized *(extrude / 2 + edgeWidth));

                        outer.Add(position + Vector3.Cross(tangent, Vector3.up).normalized *extrude / 2);
                        outer.Add(position + Vector3.Cross(tangent, Vector3.up).normalized *(extrude / 2 + edgeWidth));
                    }
                }

                for (int j = 0; j < waypointResolution; j++)
                {
                    t = j * 1.0f / waypointResolution;
                    Vector3 tangent;
                    position = CatmullRom.Interpolate(p0, p1, m0, m1, t, out tangent);

                    if (debug && generateWaypoints) //Waypoints
                    {
                        Debug.DrawLine(position, position + new Vector3(0, 25, 0), Color.blue);
                    }

                    if (store && generateWaypoints)
                    {
                        waypoints.Add(position);
                    }
                }
            }

            //Curve line
            for (int i = 0; i < CurveCoordinates.Length - 1; ++i)
            {
                Debug.DrawLine(CurveCoordinates[i] + transform.position, CurveCoordinates[i + 1] + transform.position, Color.cyan);
            }
        }
    }
Beispiel #11
0
    void Update()
    {
        Vector3 p0;
        Vector3 p1;
        Vector3 m0;
        Vector3 m1;
        int     pointsToMake;

        //Debug.Log("This is a test " + Points[0]);

        if (ClosedLoop == true)
        {
            pointsToMake = (CurveResolution) * (Points.Count);
        }
        else
        {
            pointsToMake = (CurveResolution) * (Points.Count - 1);
        }

        CurveCoordinates = new Vector3[pointsToMake];
        Tangents         = new Vector3[pointsToMake];

        int closedAdjustment = ClosedLoop ? 0 : 1;

        // First for loop goes through each individual control point and connects it to the next, so 0-1, 1-2, 2-3 and so on
        for (int i = 0; i < Points.Count - closedAdjustment; i++)
        {
            //if (Points[i] == null || Points[i + 1] == null || (i > 0 && Points[i - 1] == null) || (i < Points.Count - 2 && Points[i + 2] == null))
            //{
            //    return;
            //}

            p0 = Points[i].transform.position;
            p1 = (ClosedLoop == true && i == Points.Count - 1) ? Points[0].transform.position : Points[i + 1].transform.position;

            // Tangent calculation for each control point
            // Tangent M[k] = (P[k+1] - P[k-1]) / 2
            // With [] indicating subscript

            // m0
            if (i == 0)
            {
                m0 = ClosedLoop ? 0.5f * (p1 - Points[Points.Count - 1].transform.position) : p1 - p0;
            }
            else
            {
                m0 = 0.5f * (p1 - Points[i - 1].transform.position);
            }

            // m1
            if (ClosedLoop)
            {
                if (i == Points.Count - 1)
                {
                    m1 = 0.5f * (Points[(i + 2) % Points.Count].transform.position - p0);
                }
                else if (i == 0)
                {
                    m1 = 0.5f * (Points[i + 2].transform.position - p0);
                }
                else
                {
                    m1 = 0.5f * (Points[(i + 2) % Points.Count].transform.position - p0);
                }
            }
            else
            {
                if (i < Points.Count - 2)
                {
                    m1 = 0.5f * (Points[(i + 2) % Points.Count].transform.position - p0);
                }
                else
                {
                    m1 = p1 - p0;
                }
            }

            Vector3 position;
            float   t;
            float   pointStep = 1.0f / CurveResolution;

            if ((i == Points.Count - 2 && ClosedLoop == false) || (i == Points.Count - 1 && ClosedLoop))
            {
                pointStep = 1.0f / (CurveResolution - 1);
                // last point of last segment should reach p1
            }
            // Second for loop actually creates the spline for this particular segment
            for (int j = 0; j < CurveResolution; j++)
            {
                t = j * pointStep;
                Vector3 tangent;
                position = CatmullRom.Interpolate(p0, p1, m0, m1, t, out tangent);
                CurveCoordinates[i * CurveResolution + j] = position;
                Tangents[i * CurveResolution + j]         = tangent;
                //Debug.DrawRay(position, tangent.normalized * 2, Color.red);
                Debug.DrawLine(position + Vector3.Cross(tangent, Vector3.up).normalized, position - Vector3.Cross(tangent, Vector3.up).normalized, Color.red);
            }
        }

        for (int i = 0; i < CurveCoordinates.Length - 1; ++i)
        {
            Debug.DrawLine(CurveCoordinates[i], CurveCoordinates[i + 1]);
        }
    }