//Calculate the line from start to destination and divide into several intensity points based on fading speed and travelling speed
        private void UpdateMapPoint(SoundSegment segment, float steps, bool includeDestination)
        {
            List <Vector3> positions   = new List <Vector3>();
            List <float>   intensities = new List <float>();

            for (int k = 0; k < steps; k++)
            {
                // operation for each in between sound points
                float   length    = k * system.stepDistance;
                float   intensity = (segment._volume - system.fadingSpeed * k);
                Vector3 position  = segment._ray.origin + segment._ray.direction * length;

                positions.Add(position);
                intensities.Add(intensity);
            }

            //map it to the grid based map in the system controller
            system.MapSoundData(positions, intensities, segment, gameObject);

            // this is for dynamically adjust the length ray indicator, visual representation use only
            float intensityAtDestination = (segment._volume - system.fadingSpeed * steps) + 0.1f;

            if (includeDestination)
            {
                float length = steps * system.stepDistance;
            }
        }
        public void AddNewSoundPoint(float intensity, SoundSegment seg, string sourceName)
        {
            //filter the sound by type
            //if under the source's name there is no entry, we create one
            //else we add the intensity to the new list

            //unifypointdata averages the intensity from the same source and segment
            if (!sources.ContainsKey(seg._type))
            {
                sources.Add(seg._type, new List <SoundMapPointData>());

                SoundMapPointData data = new SoundMapPointData(seg._type, seg._id, sourceName);
                data.UnifyPointData(intensity);
                sources[seg._type].Add(data);
            }
            else
            {
                //try to find the entry that has the same segment id
                for (int i = 0; i < sources[seg._type].Count; i++)
                {
                    if (sources[seg._type][i].segmentId == seg._id && sources[seg._type][i].sourceName == sourceName)
                    {
                        sources[seg._type][i].UnifyPointData(intensity);
                        return;
                    }
                }

                //no match for this id
                SoundMapPointData data = new SoundMapPointData(seg._type, seg._id, sourceName);
                data.UnifyPointData(intensity);
                sources[seg._type].Add(data);
            }
        }
        private void ReflectSoundRay(SoundSegment segment, float remainVol, Ray ray, Vector3 direction, RaycastHit hit)
        {
            Vector3 reflectVec = Vector3.Reflect(ray.direction, hit.normal);

            //calculate remain volume at this point
            float distance  = hit.distance;
            float nextSteps = distance / system.stepDistance;

            float remain = (remainVol - nextSteps * system.fadingSpeed) * system.absorbtionRate;

            //Debug.Log("ray " + ray.origin + " " + hit.point);
            if (drawDebugLine)
            {
                Debug.DrawRay(ray.origin, direction * hit.distance, Color.blue, 0.1f);
            }

            UpdateMapPoint(segment, nextSteps, false);

            //keep reflecting, treat it as a new sound
            //soundSegmentQueue.Enqueue(new SoundSegment(segment._id + 1, hit.point, reflectVec, remain, segment._type, segment._reflectNum + 1));
            soundSegmentQueue_nextFrame.Enqueue(new SoundSegment(segment._id + 1, hit.point, reflectVec, remain, segment._type, segment._reflectNum + 1));
        }
        public void Calculate()
        {
            if (soundQueue.Count <= 0 && soundSegmentQueue.Count <= 0)
            {
                return;
            }
            if (segmentId > 10000)
            {
                segmentId = 0;
            }                                           //reset segment id

            //segmentId = 0;
            diffractedSegments.Clear();

            mapKeyPoints = new List <MapPointData>();

            soundSegmentQueue_nextFrame = new Queue <SoundSegment>();

            int numOfSoundForOneRound = soundQueue.Count;

            for (int q = 0; q < numOfSoundForOneRound; q++)
            {
                Sound sound = soundQueue.Dequeue();

                if (!sound.IsOver())
                {
                    soundQueue.Enqueue(sound);

                    float volume = sound.volume; // rayFrequency;

                    float steps          = sound.volume / (system.fadingSpeed + float.Epsilon);
                    float spreadDistance = steps * system.stepDistance;

                    //mapKeyPoints.Add(new MapPointData(sound.sid, sound.producedPos, sound.volume, fadingSpeed, stepDistance));

                    Transform target  = sound.producer.transform;
                    Vector3   origin  = target.position;
                    Vector3   forward = target.forward;

                    //rotate clockwise and counter-clockwise to find the bondary vectors
                    float   halfRange = sound.range / 2;
                    Vector3 bound_CCW = UtilityMethod.RotateAroundY(halfRange, forward);
                    Vector3 bound_CW  = UtilityMethod.RotateAroundY(-halfRange, forward);
                    //Debug.DrawRay(agent.transform.position, bound_CCW * 10, Color.cyan);
                    //Debug.DrawRay(agent.transform.position, bound_CW * 10, Color.green);

                    float   delta  = 360f / rayFrequency;
                    int     rayNum = (int)(sound.range / delta);
                    Vector3 dir    = bound_CW;
                    for (int i = 0; i <= rayNum; i++)   // <= because we enqueue the segement first then increment the angle, so we need extra 1 time for the last direction
                    {
                        //raycast
                        soundSegmentQueue.Enqueue(new SoundSegment(segmentId, origin, dir, sound.volume, sound.type, 0));
                        dir = UtilityMethod.RotateAroundY(delta, dir);
                    }
                }
            }
            while (soundSegmentQueue.Count > 0)
            {
                SoundSegment segment = soundSegmentQueue.Dequeue();

                RayCastFromPoint(segment);
            }
            soundSegmentQueue = soundSegmentQueue_nextFrame;
        }
        private void DiffractSoundRay(Vector3 diffractionPoint, Vector3 mainDir, Vector3 edgeDir, float angle, SoundSegment segment, float remainVol, float triggerRadius)
        {
            float diffractionAngle = angle * SystemController.Instance.diffractionAngleRatio;
            int   numOfRays        = Mathf.RoundToInt((float)rayFrequency / 360 * Mathf.Abs(diffractionAngle));
            float delta            = diffractionAngle / numOfRays;

            Vector3 dir    = mainDir;
            Vector3 origin = new Vector3(diffractionPoint.x, transform.position.y, diffractionPoint.z);

            for (int i = 0; i <= numOfRays; i++)
            {
                //if raycast inside the diffraction trigger, the ray will first hit the trigger from inside, so to prevent that we need to manually set the origin outside the trigger radius
                soundSegmentQueue.Enqueue(new SoundSegment(segment._id, origin + dir * triggerRadius, dir, remainVol, segment._type, segment._reflectNum));
                dir = UtilityMethod.RotateAroundY(delta, dir);
            }
        }
        private void RayCastFromPoint(SoundSegment segment)
        {
            float remainVol = segment._volume;

            if (remainVol < 0.1f || segment._reflectNum > system.reflectionLimit)
            {
                return;
            }

            //mapKeyPoints.Add(new MapPointData(sound.sid, last_hit.point, remainVol, fadingSpeed, stepDistance));

            Ray     ray       = segment._ray;
            Vector3 direction = ray.direction;

            //calculate the reflection and start volume
            //shoot ray again
            float steps      = remainVol / (system.fadingSpeed + float.Epsilon);
            float spreadDist = steps * system.stepDistance;

            _collider.enabled = false;
            Physics.Raycast(ray, out RaycastHit hit, spreadDist, raycastLayer, QueryTriggerInteraction.Collide);
            _collider.enabled = true;

            if (hit.transform == null)
            {
                //nothing
                if (drawDebugLine)
                {
                    Debug.DrawRay(ray.origin, direction * spreadDist, Color.red, 0.1f);
                }

                UpdateMapPoint(segment, steps, true);
            }
            else if (hit.transform.gameObject.GetComponent <AgentSoundComponent>())
            {
                if (drawDebugLine)
                {
                    Debug.DrawRay(ray.origin, direction * spreadDist, Color.red, 0.1f);
                }

                //calculate the remained volume
                float distance  = hit.distance;
                float nextSteps = distance / system.stepDistance;               //the new number of steps based on the current hit point of the ray
                float remain    = (remainVol - nextSteps * system.fadingSpeed); //remained volume at the hit point

                UpdateMapPoint(segment, nextSteps, false);

                //if the hit target is an Agent we notify them
                CheckHitTarget(hit.transform.gameObject, segment._type, gameObject);
            }
            else if (hit.transform.tag == "DiffractionPoint")
            {
                if (drawDebugLine)
                {
                    Debug.DrawLine(ray.origin, hit.point, Color.red, 0.1f);
                }
                // if this segment is already diffracted then we don't need to do it again
                //if (diffractedSegments.Contains(segment._id))
                //{
                //    return;
                //}
                //else
                //{
                //    diffractedSegments.Add(segment._id);
                //}

                //calculate the remained volume
                float distance  = hit.distance;
                float nextSteps = distance / system.stepDistance;                                                           //the new number of steps based on the current hit point of the ray
                float remain    = (remainVol - nextSteps * system.fadingSpeed) * SystemController.Instance.diffractionRate; //remained volume at the hit point

                UpdateMapPoint(segment, nextSteps, false);

                //handle diffraction
                Vector3 edge  = Vector3.zero;
                float   angle = 0;

                //find signed triangle with direction and two edge vectors
                DiffractionPoint diffracPoint = hit.transform.gameObject.GetComponent <DiffractionPoint>();
                Vector3          dir          = ray.direction;
                float            dotProduct_1 = Vector3.Dot(dir, diffracPoint.edgeVector_1);
                float            dotProduct_2 = Vector3.Dot(dir, diffracPoint.edgeVector_2);

                if (dotProduct_1 > 0 && dotProduct_2 > 0)
                {
                    //hit into the wall, just reflection or do nothing
                    //ReflectSoundRay(segment, sound, remainVol, ray, direction, hit);
                    Debug.DrawRay(hit.transform.position, edge * 10);
                }
                else if (dotProduct_1 < 0 && dotProduct_2 < 0)
                {
                    //nothing for now
                }
                else if (dotProduct_1 > dotProduct_2)
                {
                    edge  = diffracPoint.edgeVector_1;
                    angle = Vector3.SignedAngle(edge, ray.direction, Vector3.up);
                    if (drawDebugLine)
                    {
                        Debug.DrawRay(hit.transform.position, edge * 10, Color.green, 0.1f);
                    }
                }
                else if (dotProduct_2 > dotProduct_1)
                {
                    edge  = diffracPoint.edgeVector_2;
                    angle = Vector3.SignedAngle(edge, ray.direction, Vector3.up);
                    if (drawDebugLine)
                    {
                        Debug.DrawRay(hit.transform.position, edge * 10, Color.green, 0.1f);
                    }
                }
                else
                {
                    Debug.Log("impossible happened, both less than 0");
                }

                //after find the correct edge vector, we calculate a diffraction angle in between
                if (edge != Vector3.zero)
                {
                    DiffractSoundRay(diffracPoint.transform.position, ray.direction, edge, angle, segment, remain, diffracPoint.TriggerRadius());
                }
            }
            else
            {
                //if (segment._reflectNum >= 1) { Debug.Log("yeah"); }
                ReflectSoundRay(segment, remainVol, ray, direction, hit);
            }
        }
        public void MapSoundData(List <Vector3> positions, List <float> newIntensities, SoundSegment seg, GameObject source)
        {
            count = positions.Count;
            for (int i = 0; i < count; i++)
            {
                float x = positions[i].x - startPoint.x;
                float z = positions[i].z - startPoint.z;

                int cellX = Mathf.RoundToInt(x / soundMapTileSize);
                int cellY = Mathf.RoundToInt(z / soundMapTileSize);

                if (cellX < resolution.x && cellY < resolution.y && cellX >= 0 && cellY >= 0)
                {
                    //Debug.Log(cellX + ", " + cellY);
                    map[cellX, cellY].GetComponent <PointIntensity>().AddNewSoundPoint(newIntensities[i], seg, source.name);

                    //add this point to modified list
                    int key = cellX * (int)resolution.y + cellY;
                    int value;
                    if (!modifiedMapGrids.TryGetValue(key, out value))
                    {
                        //test if the value is already exist in the dictionary, if not we add this value as both key and value
                        modifiedMapGrids.Add(key, key);
                    }

                    //draw yellow line to indicate the mapping
                    if (drawPointDistribution)
                    {
                        Debug.DrawLine(positions[i], map[cellX, cellY].transform.position, Color.yellow);
                    }
                }
            }
        }