Skip to content
Snippets Groups Projects
WorldCursor.cs 10.80 KiB
using System;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System.Linq;
using static GadgetManager;

public class WorldCursor : MonoBehaviour
{
    public RaycastHit Hit;
    // TODO experimentell for multiple hits
    public RaycastHit[] MultipleHits;

    public string deactivateSnapKey;
    private Camera Cam;
    private int layerMask;
    public LayerMask snapLayerMask;
    public float MaxRange = 10f;
    public bool useCamCurser = false;

    private void Awake()
    {
        this.layerMask = LayerMask.GetMask("Player", "TalkingZone");
        //Ignore player and TalkingZone
        this.layerMask = ~this.layerMask;
    }

    void Start()
    {
        Cam = Camera.main;
        //Set MarkPointMode as the default ActiveToolMode
        // ActiveToolMode = ToolMode.ExtraMode;//ToolMode.MarkPointMode;
        // CommunicationEvents.ToolModeChangedEvent.Invoke(activeGadget.id);
        CultureInfo.CurrentCulture = new CultureInfo("en-US");
    }

    public void setLayerMask(int layerMask)
    {
        this.layerMask = layerMask;
    }

    //void Update()
    //{
    //    Ray ray = useCamCurser ? new Ray(Cam.transform.position, Cam.transform.forward) : Cam.ScreenPointToRay(Input.mousePosition);

    //    this.Hit = new RaycastHit();
    //    transform.up = Cam.transform.forward;
    //    transform.position = ray.GetPoint(GlobalBehaviour.GadgetPhysicalDistance);

    //    int rayCastMask;
    //    if (Input.GetButton(this.deactivateSnapKey))
    //        rayCastMask = this.layerMask & ~this.snapLayerMask.value;
    //    else
    //        rayCastMask = this.layerMask;

    //    if (Physics.Raycast(ray, out Hit, MaxRange, rayCastMask)
    //        || (MaxRange <= GlobalBehaviour.GadgetPhysicalDistance 
    //        && Physics.Raycast(transform.position, Vector3.down, out Hit, GlobalBehaviour.GadgetPhysicalDistance, rayCastMask)))
    //    {
    //        if ((Hit.collider.transform.CompareTag("SnapZone") || Hit.collider.transform.CompareTag("Selectable")) 
    //            && !Input.GetButton(this.deactivateSnapKey))
    //        {
    //            if(Hit.collider.gameObject.layer == LayerMask.NameToLayer("Ray")
    //                || Hit.collider.gameObject.layer == LayerMask.NameToLayer("Line"))
    //            {
    //                var id = Hit.collider.gameObject.GetComponent<FactObject>().URI;
    //                AbstractLineFact lineFact = StageStatic.stage.factState[id] as AbstractLineFact;
    //                PointFact p1 =  StageStatic.stage.factState[lineFact.Pid1] as PointFact;

    //                Hit.point = Math3d.ProjectPointOnLine(p1.Point, lineFact.Dir, Hit.point);
    //            }
    //            else
    //            {
    //                Hit.point = Hit.collider.transform.position;
    //                Hit.normal = Vector3.up;
    //            }

    //            transform.position = Hit.point;
    //            transform.up = Hit.normal;

    //        }
    //        else
    //        {
    //            transform.position = Hit.point;
    //            transform.up = Hit.normal;
    //            transform.position += .01f * Hit.normal;
    //        }

    //        CheckMouseButtons();

    //    }
    //}


    // working currently to include multiple hits 
    // TODO 

    void Update()
    {
        Ray ray = useCamCurser ? new Ray(Cam.transform.position, Cam.transform.forward) : Cam.ScreenPointToRay(Input.mousePosition);

        this.Hit = new RaycastHit();
        transform.up = Cam.transform.forward;
        transform.position = ray.GetPoint(GlobalBehaviour.GadgetPhysicalDistance);

        int rayCastMask;
        if (Input.GetButton(this.deactivateSnapKey))
            rayCastMask = this.layerMask & ~this.snapLayerMask.value;
        else
            rayCastMask = this.layerMask;

        // in case we dont hit anything, just return
        if (!(Physics.Raycast(ray, out Hit, MaxRange, rayCastMask)
            || (MaxRange <= GlobalBehaviour.GadgetPhysicalDistance
            && Physics.Raycast(transform.position, Vector3.down, out Hit, GlobalBehaviour.GadgetPhysicalDistance, rayCastMask))))
            return;

        RaycastHit[] multipleHits = Physics.RaycastAll(ray, MaxRange, rayCastMask);
        if (multipleHits.Length == 0)
            multipleHits = Physics.RaycastAll(transform.position, Vector3.down, GlobalBehaviour.GadgetPhysicalDistance, rayCastMask);



        // sort multipleHits, so the first hit is still the closest 
        for (int i = 0; i < multipleHits.Length; i++)
        {
            int minIdx = i;
            float minValue = multipleHits[i].distance;

            for (int j = i; j < multipleHits.Length; j++)
            {
                if (multipleHits[j].distance < minValue)
                {
                    minIdx = j;
                    minValue = multipleHits[j].distance;
                }
            }

            RaycastHit buffer = multipleHits[minIdx];
            multipleHits[minIdx] = multipleHits[i];
            multipleHits[i] = buffer;

        }
            

        for (int i = 0; i < multipleHits.Length; i++)
        {
            // check whether we actually hit something 
            if (!((multipleHits[i].collider.transform.CompareTag("SnapZone") || multipleHits[i].collider.transform.CompareTag("Selectable"))
                && !Input.GetButton(this.deactivateSnapKey)))
                continue;

            if (multipleHits[i].collider.gameObject.layer == LayerMask.NameToLayer("Ray")
                || multipleHits[i].collider.gameObject.layer == LayerMask.NameToLayer("Line"))
            {
                var id = multipleHits[i].collider.gameObject.GetComponent<FactObject>().URI;
                AbstractLineFact lineFact = StageStatic.stage.factState[id] as AbstractLineFact;
                PointFact p1 = StageStatic.stage.factState[lineFact.Pid1] as PointFact;

                multipleHits[i].point = Math3d.ProjectPointOnLine(p1.Point, lineFact.Dir, multipleHits[i].point);
            }
            else if (multipleHits[i].collider.gameObject.layer == LayerMask.NameToLayer("Ring"))
            {
                #region Ring
                var id = multipleHits[i].transform.GetComponent<FactObject>().URI;
                CircleFact circleFact = StageStatic.stage.factState[id] as CircleFact;
                Vector3 middlePoint = ((PointFact)StageStatic.stage.factState[circleFact.Pid1]).Point;
                Vector3 edgePoint = ((PointFact)StageStatic.stage.factState[circleFact.Pid2]).Point;
                var normal = circleFact.normal;
                var radius = circleFact.radius;

                // project p on circlePlane
                var q = multipleHits[i].point - middlePoint;
                var dist = Vector3.Dot(q, normal);
                var pPlane = multipleHits[i].point - (normal * dist); // p on circlePlane

                // check if projectedPoint and circleCenter are identical
                // should never happen in practice due to floating point precision
                if (pPlane == middlePoint)
                {
                    // can be set to any point on the ring -> set to edgePoint
                    multipleHits[i].point = edgePoint;
                    return;
                }
                else
                {
                    var direction = (pPlane - middlePoint).normalized;
                    multipleHits[i].point = middlePoint + direction * radius;
                }
                // cursor orientation should match circle orientation; dont face downwards
                if (normal.y < 0) // if normal faces downwards use inverted normal instead
                    multipleHits[i].normal = -normal;
                else
                    multipleHits[i].normal = normal;
                #endregion Ring
            }
            else
            {
                multipleHits[i].point = multipleHits[i].collider.transform.position;
                multipleHits[i].normal = Vector3.up;
            }

            // checking for 2 lines intersection point
            if (!((Mathf.Abs(multipleHits[i].distance - multipleHits[0].distance) < 0.03)
                && (multipleHits.Length > 1)
                && (Mathf.Abs(multipleHits[1].distance - multipleHits[0].distance) < 0.03)))
                continue;
            // we probably have two objects intersecting 

            
            // check for line x line intersection and if they actually intersect adjust the points coordinates :)
            if (multipleHits[i].collider.gameObject.layer == LayerMask.NameToLayer("Ray")
                && multipleHits[0].collider.gameObject.layer == LayerMask.NameToLayer("Ray"))
            {

                // case for two intersecting rays 
                var idLine0 = multipleHits[0].collider.gameObject.GetComponent<FactObject>().URI;
                var id = multipleHits[i].collider.gameObject.GetComponent<FactObject>().URI;

                // get the two corresponding line facts
                AbstractLineFact lineFactLine0 = StageStatic.stage.factState[idLine0] as AbstractLineFact;
                AbstractLineFact lineFact = StageStatic.stage.factState[id] as AbstractLineFact;

                // get a point on the line 
                PointFact p1Line0 = StageStatic.stage.factState[lineFactLine0.Pid1] as PointFact;
                PointFact p1 = StageStatic.stage.factState[lineFact.Pid1] as PointFact;

                // get the intersection point and if it actually intersects set it
                Vector3 intersectionPoint = Vector3.zero;
 
                if (Math3d.LineLineIntersection(out intersectionPoint, p1Line0.Point, lineFactLine0.Dir, p1.Point, lineFact.Dir))
                    multipleHits[i].point = intersectionPoint;


            }
            //check for other types of intersection. Future Work






        }

        transform.position = multipleHits[0].point;
        transform.up = multipleHits[0].normal;
        if (!((multipleHits[0].collider.transform.CompareTag("SnapZone") || multipleHits[0].collider.transform.CompareTag("Selectable"))
              && !Input.GetButton(this.deactivateSnapKey)))
            transform.position += .01f * multipleHits[0].normal;






        this.MultipleHits = multipleHits;
        CheckMouseButtons();



    }





    //Check if left Mouse-Button was pressed and handle it
    void CheckMouseButtons()
    {
        //TODO massively edit for the multiple hits. Right now it only checks the first hit
        if (Input.GetMouseButtonDown(0))
        {
            if (EventSystem.current.IsPointerOverGameObject() //this prevents rays from shooting through ui
                || Hit.transform.gameObject.layer == LayerMask.NameToLayer("Water")) // not allowed to meassure on water
                return;
            CommunicationEvents.TriggerEvent.Invoke(MultipleHits);
        }
    }

}