using HighwayRacers; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.Rendering; using Unity.Transforms; using UnityEngine; public class CarMovementSystem : SystemBase { public float3 TrackOrigin = new float3(0,0.25f,0); public uint Frame => _Frame; private const float CircleRadians = 2 * math.PI; private uint _Frame = 0; private TrackOccupancySystem m_TrackOccupancySystem; private StreetDefinitionSystem m_StreetDefinitionSystem; private Unity.Mathematics.Random randomObj; //------------------------------------------------------------------------------------------------ protected override void OnCreate() { CommonFields.LogDebug(); UnityEngine.Debug.Log(">> CarMovementSystem Created."); m_TrackOccupancySystem = World.GetExistingSystem(); m_StreetDefinitionSystem = World.GetExistingSystem(); randomObj = new Unity.Mathematics.Random(1234); m_GroupStreet = GetEntityQuery(typeof(StreetDataElement)); } EntityQuery m_GroupStreet; //NativeArray streetDefinition; //------------------------------------------------------------------------------------------------ protected override void OnUpdate() { _Frame++; float deltaTime = Time.DeltaTime; float trackRadius = CommonFields.TrackRadius; float3 trackOrigin = TrackOrigin; float laneWidth = CommonFields.LaneWidth; //int tilesPerLane = CommonFields.TilesPerLane; //int laneCount = CommonFields.NUM_LANES; uint theFrame = _Frame; var random = randomObj; var readOccupancy = m_TrackOccupancySystem.GetReadBuffer(_Frame); //if (streetDefinition.Length == 0) { // Debug.LogWarning("Buffer length = 0!"); // return; //} //float4 lowVelocityColor = CommonFields.LowVelocityColor; //float4 americanColors = CommonFields.AmericanColors; //float4 europeanColors = CommonFields.EuropeanColors; var streetslength = m_GroupStreet.CalculateEntityCount(); for(int i=0;i< streetslength; i++) { var streetDefinition = m_StreetDefinitionSystem.GetBuffer(i); var streetTraffic = m_StreetDefinitionSystem.GetTraffic(i); Entities .WithNativeDisableContainerSafetyRestriction(streetDefinition) .WithNativeDisableContainerSafetyRestriction(readOccupancy) .ForEach((ref Translation translation, ref Rotation rotation, ref CarMovement movement, ref URPMaterialPropertyBaseColor baseColor) => { if (movement.Street == i) { int laneCount = streetTraffic.LanesA; bool shown = true; if (streetTraffic.VolTrafficAB < ((double)movement.CarIndex / (double)streetslength)) { shown = false; movement.IsShown = false; } else { if (movement.IsShown == false) { movement.IsShown = true;// initialize movement.Offset = random.NextFloat(); } } // Limit cars from switching lanes too frequently movement.LaneSwitchCounter -= deltaTime; int tilesPerLane = streetDefinition.Length; //UnityEngine.Debug.Log(string.Format("R:{0} G:{1} B:{2}", math.round(baseColor.Value.x), math.round(baseColor.Value.y), math.round(baseColor.Value.z))); // Get occupancy of nearby tiles int myTile = TrackOccupancySystem.GetMyTile(movement.Offset, tilesPerLane); int nextTile = (myTile + 1) % tilesPerLane; int prevTile = math.max(myTile - 1, 0) % tilesPerLane; bool nextIsOccupied = TrackOccupancySystem.IsTileOccupied(ref readOccupancy, (int)movement.Lane, nextTile, i); // If car is an European driver and it is blocking a car behind it, the driver // will attempt to switch to a more inner lane. // If the driver is American, it will exercise its constitutional rights and stay in its lane. bool favorInnerLane = false; if (movement.Profile == DriverProfile.European) { favorInnerLane = TrackOccupancySystem.IsTileOccupied(ref readOccupancy, (int)movement.Lane, prevTile, i); } // Make a random decision to switch lanes when blocked bool randomlySwitchLanes = random.NextInt(0, 100) > 33; // Decide to switch lanes if ((nextIsOccupied || favorInnerLane) && movement.LaneSwitchCounter <= 0 && randomlySwitchLanes) { // To avoid having two cars merge into the same lane, we allow // mergers to the right at even frames and merges to the left at odd frames. int sideLane = (int)movement.Lane; bool isEven = theFrame % 2 == 0; if (isEven && !favorInnerLane) { sideLane = sideLane + 1 < laneCount ? sideLane + 1 : (int)movement.Lane; } else { sideLane = sideLane - 1 >= 0 ? sideLane - 1 : (int)movement.Lane; } if (sideLane != movement.Lane) { // Require 3 un-occupied slots for switching lanes bool sideIsOccupied = TrackOccupancySystem.IsTileOccupied(ref readOccupancy, sideLane, myTile,i); bool nextSideIsOccupied = TrackOccupancySystem.IsTileOccupied(ref readOccupancy, sideLane, nextTile,i); bool prevSideIsOccupied = TrackOccupancySystem.IsTileOccupied(ref readOccupancy, sideLane, prevTile,i); //bool finishedChangingLane = math.abs(movement.LaneOffset - movement.Lane) < 0.05; if (!sideIsOccupied && !nextSideIsOccupied && !prevSideIsOccupied) { movement.Lane = sideLane; movement.LaneSwitchCounter = random.NextFloat(5, 10); } } } // All cars move at the minimum required speed on the highway. // (We don't want cars to stop if the tile in front is occupied) // We lerp velocity changes over time so it looks like the car accelerates. movement.CurrentVelocity = nextIsOccupied ? CommonFields.MinimumVelocity : math.lerp(movement.CurrentVelocity, movement.Velocity, deltaTime * 2); // Turn cars red the longer they cannot reach their desired speed // We lerp this over time so we don't flicker. if (movement.CurrentVelocity < movement.Velocity - 0.001f) { baseColor.Value = math.lerp(baseColor.Value, CommonFields.LowVelocityColor, deltaTime); //baseColor.Value = math.lerp(baseColor.Value, lowVelocityColor, deltaTime); } else { baseColor.Value = baseColor.Value = math.lerp(baseColor.Value, movement.Profile == DriverProfile.American ? CommonFields.AmericanColors : CommonFields.EuropeanColors, deltaTime * 2); } // LaneOffset is the physical lane position of the car while Lane // is the lane the car wants to be in. Let's make progress to // merge towards that lane movement.LaneOffset = movement.LaneOffset + ((float)movement.Lane - movement.LaneOffset) * deltaTime * 5.0f; /* ORIGINAL */ /* * * * var position = streetDefinition[myTile].StreetPosition; var direction = streetDefinition[nextTile].StreetPosition - streetDefinition[myTile].StreetPosition; Vector3 left = Vector3.Cross(direction, Vector3.up).normalized; var displacement = position - (float3)left * movement.LaneOffset; var realoffset = ((movement.Offset * tilesPerLane + 0.5f) - myTile); var positionoffset = displacement + direction * realoffset; movement.angle = streetDefinition[myTile].StreetPosition.y; translation.Value.x = positionoffset.x; //myTile translation.Value.z = positionoffset.z; if (nextTile < myTile) { translation.Value.y = -1000; } else { translation.Value.y = 0; } // Move car forward on its track movement.Offset += movement.CurrentVelocity * deltaTime; movement.Offset = movement.Offset % 1.0f; // Rotate based on where it is on the rounded rect rotation.Value = Quaternion.Euler(0, movement.angle, 0);// quaternion.EulerYXZ(0, movement.angle , 0); */ int initialLane = movement.Lane; /* FABIAN VERSION*/ // if (nextTile < streetDefinition.Length) float3 P2 = (nextTile - 1 < 0) ? streetDefinition[streetDefinition.Length - 1].StreetPosition : streetDefinition[nextTile - 1].StreetPosition; float3 P1 = (nextTile + 1 >= streetDefinition.Length - 1) ? streetDefinition[0].StreetPosition : streetDefinition[nextTile + 1].StreetPosition; float3 carPos = PositionLane(P2, streetDefinition[nextTile].StreetPosition, P1, movement.Lane, initialLane > movement.Lane || initialLane < movement.Lane); float3 P2_ = streetDefinition[nextTile].StreetPosition; float3 P1_ = (nextTile + 2 >= streetDefinition.Length - 2) ? streetDefinition[0].StreetPosition: streetDefinition[nextTile + 2].StreetPosition; float3 carPos_ = PositionLane(P2, (nextTile + 1 >= streetDefinition.Length - 1) ? streetDefinition[0].StreetPosition : streetDefinition[nextTile + 1].StreetPosition, P1, movement.Lane); //2.0 3.5 -> ok //2.5 0.5 -> OK. Me gusta //1.5 3.5 -> ok- se cierran un poco en curvas //1.0 3.5 -> bad. se cierra demaciado en las curvas //0.5 3.5 -> muy mal. se cierrra en todos lados - demaciada asceleración y dessaceleración //0.5 1.5 -> muy mal. se cierrra en todos lados float3 smothMove = math.lerp(translation.Value, carPos, deltaTime * (2.5f + 0.5f * ((movement.CurrentVelocity - CommonFields.MinimumVelocity) / (CommonFields.MaximumVelocity - CommonFields.MinimumVelocity)))); float3 diff = carPos_ - smothMove; if (myTile > 0) { translation.Value = smothMove; rotation.Value = quaternion.LookRotation(diff, new float3(0, 1, 0)); } else { // when the segment starts force the car appear at the beginning rotation.Value = quaternion.LookRotation(carPos, new float3(0, 1, 0)); translation.Value = carPos; } if (nextTile < myTile) { rotation.Value = quaternion.LookRotation(carPos_, new float3(0, 1, 0)); translation.Value = carPos; } // Move car forward on its track movement.Offset += movement.CurrentVelocity * deltaTime; movement.Offset = movement.Offset % 1.0f; if (!shown) { translation.Value = new float3(0, -1000, 0); } } } ) .ScheduleParallel(); } } //------------------------------------------------------------------------------------------------ static float3 MapToRoundedCorners(float t, float radius) { float R = CommonFields.RoundedCorner; float straight = 1.0f - 2.0f * R; float curved = (2.0f * math.PI * R) * 0.25f; float total = straight + curved; float tls = math.saturate(straight/total); float tlr = math.saturate(curved/total); int q = (int)(t * 4.0f); float x = 0; float y = 0; float a = 0; if(q == 0) { float n = t * 4.0f; x = R; y = math.lerp(R, 1.0f - R, math.saturate(n/tls)); a = 0.5f * math.PI * math.saturate((n - tls)/tlr); x -= math.cos(a) * R; y += math.sin(a) * R; } else if(q == 1) { float n = (t - 0.25f) * 4.0f; y = 1.0f - R; x = math.lerp(R, 1.0f - R, math.saturate(n/tls)); a = 0.5f * math.PI * math.saturate((n - tls)/tlr); y += math.cos(a) * R; x += math.sin(a) * R; a += math.PI/2.0f; } else if(q == 2) { float n = (t - 0.5f) * 4.0f; x = 1.0f - R; y = math.lerp(1.0f - R, R, math.saturate(n/tls)); a = 0.5f * math.PI * math.saturate((n - tls)/tlr); x += math.cos(a) * R; y -= math.sin(a) * R; a -= math.PI; } else { float n = (t - 0.75f) * 4.0f; y = R; x = math.lerp(1.0f - R, R, math.saturate(n/tls)); a = 0.5f * math.PI * math.saturate((n - tls)/tlr); y -= math.cos(a) * R; x -= math.sin(a) * R; a -= math.PI/2.0f; } x -= 0.5f; y -= 0.5f; x *= radius; y *= radius; return new float3(x,y,a); } //------------------------------------------------------------------------------------------ public static float3 PositionLane(float3 P2, float3 pos, float3 P1, int laneNumber, bool crossLane = false) { float3 b = pos; if (laneNumber > 0) { float3 v1 = P2 - P1; float3 t1 = new float3(-v1.z, v1.y, v1.x); float t1Mag = math.sqrt(t1.x * t1.x + t1.y * t1.y + t1.z * t1.z); float3 t1n = new float3(-v1.z / t1Mag, v1.y / t1Mag, v1.x / t1Mag); b = pos - t1n * (CommonFields.TILE_DIAMETER / (crossLane ? 2f : 1f)) * laneNumber; } return b; } }