Exemple #1
0
 public Car()
 {
     intersectionSpline = new TrackSpline();
 }
Exemple #2
0
    public void Update()
    {
        TrackSpline currentSpline = roadSpline;
        Vector2     extrudePoint;

        if (isInsideIntersection)
        {
            currentSpline = intersectionSpline;
        }

        // acceleration
        normalizedSpeed += Time.deltaTime * 2f;
        if (normalizedSpeed > 1f)
        {
            normalizedSpeed = 1f;
        }

        // splineTimer goes from 0 to 1, so we need to adjust our speed
        // based on our current spline's length.
        splineTimer += normalizedSpeed * maxSpeed / currentSpline.measuredLength * Time.deltaTime;

        List <Car> queue = null;

        if (isInsideIntersection == false)
        {
            float approachSpeed = 1f;

            // find other cars in our lane
            queue = roadSpline.GetQueue(splineDirection, splineSide);
            int index = queue.IndexOf(this);

            if (index > 0)
            {
                // someone's ahead of us - don't clip through them
                float maxT = queue[index - 1].splineTimer - roadSpline.carQueueSize;
                if (splineTimer > maxT)
                {
                    splineTimer     = maxT;
                    normalizedSpeed = 0f;
                }
                else
                {
                    // slow down when approaching another car
                    approachSpeed = (maxT - splineTimer) * 5f;
                }
            }
            else
            {
                // we're "first in line" in our lane, but we still might need
                // to slow down if our next intersection is occupied
                if (splineDirection == 1)
                {
                    if (roadSpline.endIntersection.occupied[(splineSide + 1) / 2])
                    {
                        approachSpeed = (1f - splineTimer) * .8f + .2f;
                    }
                }
                else
                {
                    if (roadSpline.startIntersection.occupied[(splineSide + 1) / 2])
                    {
                        approachSpeed = (1f - splineTimer) * .8f + .2f;
                    }
                }
            }

            if (normalizedSpeed > approachSpeed)
            {
                normalizedSpeed = approachSpeed;
            }
        }
        else
        {
            // speed penalty when we're inside an intersection
            if (normalizedSpeed > .7f)
            {
                normalizedSpeed = .7f;
            }
        }
        float t = Mathf.Clamp01(splineTimer);

        // figure out our position in "unextruded" road space
        // (top of bottom of road, on the left or right side)
        if (isInsideIntersection == false)
        {
            if (splineDirection == -1)
            {
                t = 1f - t;
            }
            extrudePoint = new Vector2(-RoadGenerator.trackRadius * .5f * splineDirection * splineSide, RoadGenerator.trackThickness * .5f * splineSide);
        }
        else
        {
            extrudePoint = new Vector2(-RoadGenerator.trackRadius * .5f * intersectionSide, RoadGenerator.trackThickness * .5f * intersectionSide);
        }

        // find our position and orientation
        Vector3 forward, up;
        Vector3 splinePoint = currentSpline.Extrude(extrudePoint, t, out forward, out up);

        up *= splineSide;

        position = splinePoint + up.normalized * .06f;

        Vector3 moveDir = position - lastPosition;

        lastPosition = position;
        if (moveDir.sqrMagnitude > 0.0001f && up.sqrMagnitude > 0.0001f)
        {
            rotation = Quaternion.LookRotation(moveDir * splineDirection, up);
        }
        matrix = Matrix4x4.TRS(position, rotation, new Vector3(.1f, .08f, .12f));

        if (splineTimer >= 1f)
        {
            // we've reached the end of our current segment

            if (isInsideIntersection)
            {
                // we're exiting an intersection - make sure the next road
                // segment has room for us before we proceed
                if (roadSpline.GetQueue(splineDirection, splineSide).Count <= roadSpline.maxCarCount)
                {
                    intersectionSpline.startIntersection.occupied[(intersectionSide + 1) / 2] = false;
                    isInsideIntersection = false;
                    splineTimer          = 0f;
                }
                else
                {
                    splineTimer     = 1f;
                    normalizedSpeed = 0f;
                }
            }
            else
            {
                // we're exiting a road segment - first, we need to know
                // which intersection we're entering
                Intersection intersection;
                if (splineDirection == 1)
                {
                    intersection = roadSpline.endIntersection;
                    intersectionSpline.startPoint = roadSpline.endPoint;
                }
                else
                {
                    intersection = roadSpline.startIntersection;
                    intersectionSpline.startPoint = roadSpline.startPoint;
                }

                // now we need to know which road segment we'll move into
                // (dead-ends force u-turns, but otherwise, u-turns are not allowed)
                int newSplineIndex = 0;
                if (intersection.neighbors.Count > 1)
                {
                    int mySplineIndex = intersection.neighborSplines.IndexOf(roadSpline);
                    newSplineIndex = Random.Range(0, intersection.neighborSplines.Count - 1);
                    if (newSplineIndex >= mySplineIndex)
                    {
                        newSplineIndex++;
                    }
                }
                TrackSpline newSpline = intersection.neighborSplines[newSplineIndex];

                // make sure that our side of the intersection (top/bottom)
                // is empty before we enter
                if (intersection.occupied[(intersectionSide + 1) / 2])
                {
                    splineTimer     = 1f;
                    normalizedSpeed = 0f;
                }
                else
                {
                    // to avoid flipping between top/bottom of our roads,
                    // we need to know our new spline's normal at our entrance point
                    Vector3 newNormal;
                    if (newSpline.startIntersection == intersection)
                    {
                        splineDirection             = 1;
                        newNormal                   = newSpline.startNormal;
                        intersectionSpline.endPoint = newSpline.startPoint;
                    }
                    else
                    {
                        splineDirection             = -1;
                        newNormal                   = newSpline.endNormal;
                        intersectionSpline.endPoint = newSpline.endPoint;
                    }

                    // now we'll prepare our intersection spline - this lets us
                    // create a "temporary lane" inside the current intersection
                    intersectionSpline.anchor1      = (intersection.position + intersectionSpline.startPoint) * .5f;
                    intersectionSpline.anchor2      = (intersection.position + intersectionSpline.endPoint) * .5f;
                    intersectionSpline.startTangent = Vector3Int.RoundToInt((intersection.position - intersectionSpline.startPoint).normalized);
                    intersectionSpline.endTangent   = Vector3Int.RoundToInt((intersection.position - intersectionSpline.endPoint).normalized);
                    intersectionSpline.startNormal  = intersection.normal;
                    intersectionSpline.endNormal    = intersection.normal;

                    if (roadSpline == newSpline)
                    {
                        // u-turn - make our intersection spline more rounded than usual
                        Vector3 perp = Vector3.Cross(intersectionSpline.startTangent, intersectionSpline.startNormal);
                        intersectionSpline.anchor1 += (Vector3)intersectionSpline.startTangent * RoadGenerator.intersectionSize * .5f;
                        intersectionSpline.anchor2 += (Vector3)intersectionSpline.startTangent * RoadGenerator.intersectionSize * .5f;

                        intersectionSpline.anchor1 -= perp * RoadGenerator.trackRadius * .5f * intersectionSide;
                        intersectionSpline.anchor2 += perp * RoadGenerator.trackRadius * .5f * intersectionSide;
                    }

                    intersectionSpline.startIntersection = intersection;
                    intersectionSpline.endIntersection   = intersection;
                    intersectionSpline.MeasureLength();

                    isInsideIntersection = true;

                    // to maintain our current orientation, should we be
                    // on top of or underneath our next road segment?
                    // (each road segment has its own "up" direction, at each end)
                    if (Vector3.Dot(newNormal, up) > 0f)
                    {
                        splineSide = 1;
                    }
                    else
                    {
                        splineSide = -1;
                    }

                    // should we be on top of or underneath the intersection?
                    if (Vector3.Dot(intersectionSpline.startNormal, up) > 0f)
                    {
                        intersectionSide = 1;
                    }
                    else
                    {
                        intersectionSide = -1;
                    }

                    // block other cars from entering this intersection
                    intersection.occupied[(intersectionSide + 1) / 2] = true;

                    // remove ourselves from our previous lane's list of cars
                    if (queue != null)
                    {
                        queue.Remove(this);
                    }

                    // add "leftover" spline timer value to our new spline timer
                    // (avoids a stutter when changing between splines)
                    splineTimer = (splineTimer - 1f) * roadSpline.measuredLength / intersectionSpline.measuredLength;
                    roadSpline  = newSpline;

                    newSpline.GetQueue(splineDirection, splineSide).Add(this);
                }
            }
        }
    }
    IEnumerator SpawnRoads()
    {
        // first generation pass: plan roads as basic voxel data only
        trackVoxels = new bool[voxelCount, voxelCount, voxelCount];
        List <Vector3Int> activeVoxels = new List <Vector3Int>();

        trackVoxels[voxelCount / 2, voxelCount / 2, voxelCount / 2] = true;
        activeVoxels.Add(new Vector3Int(voxelCount / 2, voxelCount / 2, voxelCount / 2));

        // after voxel generation, we'll convert our network into non-voxels
        intersections        = new List <Intersection>();
        intersectionsGrid    = new Intersection[voxelCount, voxelCount, voxelCount];
        intersectionPairs    = new HashSet <long>();
        trackSplines         = new List <TrackSpline>();
        roadMeshes           = new List <Mesh>();
        intersectionMatrices = new List <List <Matrix4x4> >();

        cars        = new List <Car>();
        carMatrices = new List <List <Matrix4x4> >();
        carMatrices.Add(new List <Matrix4x4>());
        carColors = new List <List <Vector4> >();
        carColors.Add(new List <Vector4>());
        carMatProps = new MaterialPropertyBlock();
        carMatProps.SetVectorArray("_Color", new Vector4[instancesPerBatch]);


        // plan roads broadly: first, as a grid of true/false voxels
        int ticker = 0;

        while (activeVoxels.Count > 0 && ticker < 50000)
        {
            ticker++;
            int        index = Random.Range(0, activeVoxels.Count);
            Vector3Int pos   = activeVoxels[index];
            Vector3Int dir   = dirs[Random.Range(0, dirs.Length)];
            Vector3Int pos2  = new Vector3Int(pos.x + dir.x, pos.y + dir.y, pos.z + dir.z);
            if (GetVoxel(pos2) == false)
            {
                // when placing a new voxel, it must have fewer than three
                // diagonal-or-cardinal neighbors.
                // (this blocks nonplanar intersections from forming)
                if (CountNeighbors(pos2, true) < 3)
                {
                    activeVoxels.Add(pos2);
                    trackVoxels[pos2.x, pos2.y, pos2.z] = true;
                }
            }

            int neighborCount = CountNeighbors(pos);
            if (neighborCount >= 3)
            {
                // no more than three cardinal neighbors for any voxel (no 4-way intersections allowed)
                // (really, this is to avoid nonplanar intersections)
                Intersection intersection = new Intersection(pos, (Vector3)pos * voxelSize, Vector3Int.zero);
                intersection.id = intersections.Count;
                intersections.Add(intersection);
                intersectionsGrid[pos.x, pos.y, pos.z] = intersection;
                activeVoxels.RemoveAt(index);
            }

            if (ticker % 1000 == 0)
            {
                yield return(null);
            }
        }

        Debug.Log(intersections.Count + " intersections");

        // at this point, we've generated our full layout, but everything
        // is voxels, and we've identified which voxels are intersections.
        // next, we'll reinterpret our voxels as a network of intersections:
        // we'll find all "neighboring intersections" in our voxel map
        // (neighboring intersections are connected by a chain of voxels,
        // which we'll replace with splines)

        for (int i = 0; i < intersections.Count; i++)
        {
            Intersection intersection      = intersections[i];
            Vector3Int   axesWithNeighbors = Vector3Int.zero;
            for (int j = 0; j < dirs.Length; j++)
            {
                if (GetVoxel(intersection.index + dirs[j], false))
                {
                    axesWithNeighbors.x += Mathf.Abs(dirs[j].x);
                    axesWithNeighbors.y += Mathf.Abs(dirs[j].y);
                    axesWithNeighbors.z += Mathf.Abs(dirs[j].z);

                    Vector3Int   connectDir;
                    Intersection neighbor = FindFirstIntersection(intersection.index, dirs[j], out connectDir);
                    if (neighbor != null && neighbor != intersection)
                    {
                        // make sure we haven't already added the reverse-version of this spline
                        long hash = HashIntersectionPair(intersection, neighbor);
                        if (intersectionPairs.Contains(hash) == false)
                        {
                            intersectionPairs.Add(hash);

                            TrackSpline spline = new TrackSpline(intersection, dirs[j], neighbor, connectDir);
                            trackSplines.Add(spline);

                            intersection.neighbors.Add(neighbor);
                            intersection.neighborSplines.Add(spline);
                            neighbor.neighbors.Add(intersection);
                            neighbor.neighborSplines.Add(spline);
                        }
                    }
                }
            }

            // find this intersection's normal - it's the one axis
            // along which we have no neighbors
            for (int j = 0; j < 3; j++)
            {
                if (axesWithNeighbors[j] == 0)
                {
                    if (intersection.normal == Vector3Int.zero)
                    {
                        intersection.normal    = new Vector3Int();
                        intersection.normal[j] = -1 + Random.Range(0, 2) * 2;
                        //Debug.DrawRay(intersection.position,(Vector3)intersection.normal * .5f,Color.red,1000f);
                    }
                    else
                    {
                        Debug.LogError("a straight line has been marked as an intersection!");
                    }
                }
            }
            if (intersection.normal == Vector3Int.zero)
            {
                Debug.LogError("nonplanar intersections are not allowed!");
            }

            // NOTE - if you investigate the above logic, you might be confused about how
            // dead-ends are given normals, since we're assuming that all intersections
            // have two axes with neighbors and only one axis without. dead-ends only have
            // one neighbor-axis...and somehow they still get a normal without a special case.
            //
            // the "gotcha" is that the visible dead-ends in the demo have three
            // neighbors during the voxel phase, with two of their neighbor chains leading
            // to nothing. these "hanging chains" are not included as splines, so the
            // dead-ends that we see are actually "T" shapes with the top two segments hidden.

            if (i % 20 == 0)
            {
                yield return(null);
            }
        }

        Debug.Log(trackSplines.Count + " road splines");


        // generate road meshes

        List <Vector3> vertices  = new List <Vector3>();
        List <Vector2> uvs       = new List <Vector2>();
        List <int>     triangles = new List <int>();

        int triCount = 0;

        for (int i = 0; i < trackSplines.Count; i++)
        {
            trackSplines[i].GenerateMesh(vertices, uvs, triangles);

            if (triangles.Count / 3 > trisPerMesh || i == trackSplines.Count - 1)
            {
                // our current mesh data is ready to go!
                if (triangles.Count > 0)
                {
                    Mesh mesh = new Mesh();
                    mesh.name = "Generated Road Mesh";
                    mesh.SetVertices(vertices);
                    mesh.SetUVs(0, uvs);
                    mesh.SetTriangles(triangles, 0);
                    mesh.RecalculateNormals();
                    mesh.RecalculateBounds();
                    roadMeshes.Add(mesh);
                    triCount += triangles.Count / 3;
                }

                vertices.Clear();
                uvs.Clear();
                triangles.Clear();
            }

            if (i % 10 == 0)
            {
                yield return(null);
            }
        }

        // generate intersection matrices for batch-rendering
        int batch = 0;

        intersectionMatrices.Add(new List <Matrix4x4>());
        for (int i = 0; i < intersections.Count; i++)
        {
            intersectionMatrices[batch].Add(intersections[i].GetMatrix());
            if (intersectionMatrices[batch].Count == instancesPerBatch)
            {
                batch++;
                intersectionMatrices.Add(new List <Matrix4x4>());
            }
        }

        Debug.Log(triCount + " road triangles (" + roadMeshes.Count + " meshes)");


        // spawn cars

        batch = 0;
        for (int i = 0; i < 4000; i++)
        {
            Car car = new Car();
            car.maxSpeed        = carSpeed;
            car.roadSpline      = trackSplines[Random.Range(0, trackSplines.Count)];
            car.splineTimer     = 1f;
            car.splineDirection = -1 + Random.Range(0, 2) * 2;
            car.splineSide      = -1 + Random.Range(0, 2) * 2;

            car.roadSpline.GetQueue(car.splineDirection, car.splineSide).Add(car);

            cars.Add(car);
            carMatrices[batch].Add(Matrix4x4.identity);
            carColors[batch].Add(Random.ColorHSV());
            if (carMatrices[batch].Count == instancesPerBatch)
            {
                carMatrices.Add(new List <Matrix4x4>());
                carColors.Add(new List <Vector4>());
                batch++;
            }
        }
    }