using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Windows; public class Raytracer2D : MonoBehaviour { public PointLight Light; public OpticalSurface2D[] Surfaces; // Start is called before the first frame update void Start() { } bool NearestIntersection(Vector2 rayOrigin, Vector2 rayDir, OpticalSurface2D.Line[] lines, int excludeLineIndex, ref int lineIndex, ref Vector2 intersectionPoint) { Dictionary intersections = new Dictionary(); Vector2 hitPoint = new Vector2(); for (int i = 0; i < lines.Length; i++) { if(i != excludeLineIndex) { var l = lines[i]; if (RaycastMath.RayLineSegmentIntersection(rayOrigin, rayDir, l.P1, l.P2, ref hitPoint)) { intersections.Add(i, hitPoint); } } } if(intersections.Count == 0) { return false; } var result = intersections.OrderBy(x => Vector2.Distance(rayOrigin, x.Value)).First(); lineIndex = result.Key; intersectionPoint = result.Value; return true; } Vector2 Perpendicular(Vector2 vector) { return new Vector2(vector.y, -vector.x); } Vector2 Reflect(Vector2 vector, Vector2 reflectionVector) { var l = Vector2.Dot(reflectionVector, vector ); var p = reflectionVector * l; var p2 = p - vector; return p + p2; } bool Refract(Vector2 incident, Vector2 normal, float n1, float n2, out Vector2 result) { //Snell's law: n1*sin(theta1) = n2*sin(theta2) //n1/n2 = sin(theta2) / sin(theta1) //ior = sin(theta2) / sin(theta1) //sin(theta2) = ior * sin(theta1) float cosTheta1 = Vector2.Dot(incident, normal); if(cosTheta1 < 0) { cosTheta1 = -cosTheta1; } else { normal = -normal; var tmp = n1; n1 = n2; n2 = tmp; } float ior = n1 / n2; float sinTheta1Squared = 1.0f - (cosTheta1 * cosTheta1); float sinTheta2Squared = (ior * ior) * sinTheta1Squared; float cosTheta2Squared = 1.0f - sinTheta2Squared; if(cosTheta2Squared < 0.0f) { result = Vector2.zero; return false; //total internal reflection } float cosTheta2 = Mathf.Sqrt(cosTheta2Squared); float s = cosTheta1 * ior - cosTheta2; result.x = ior * incident.x + s * normal.x; result.y = ior * incident.y + s * normal.y; return true; } public int MaxTraceDepth = 2; private bool NearestRaycast(Ray2D ray, ref RaycastMath.RaycastResult2D result) { int id = 0; foreach (var surface in Surfaces) { RaycastMath.RaycastResult2D raycast = new RaycastMath.RaycastResult2D(); if (surface.Raycast(ray, ref raycast)) { if(id == 0) { result = raycast; } else { if(Vector2.Distance(ray.origin, raycast.Point) < Vector2.Distance(ray.origin, result.Point)) { result = raycast; } } id++; } } return id > 0; } private void TraceRay(Ray2D ray, int depth, float n1, float n2, List result) { if(depth >= MaxTraceDepth) { return; } RaycastMath.RaycastResult2D cast = new RaycastMath.RaycastResult2D(); if (NearestRaycast(ray, ref cast)) { // Gizmos.color = Color.blue; //Gizmos.DrawLine(cast.Point, cast.Point + cast.Normal * 0.5f); bool fromOutside = Vector2.Dot(cast.Normal, ray.direction) < 0.0f; Vector2 offset = cast.Normal * 0.0001f; if (Refract(ray.direction.normalized, cast.Normal, n1, n2, out var refractedRay)) { if (fromOutside) { offset = -offset; } result.Add(new Ray2D(cast.Point, refractedRay.normalized)); TraceRay(new Ray2D(cast.Point + offset, refractedRay.normalized), depth + 1, n1, n2, result); } else { var reflectionVector = Reflect(ray.direction.normalized, Perpendicular(cast.Normal)); //Total internal reflection result.Add(new Ray2D(cast.Point, reflectionVector)); TraceRay(new Ray2D(cast.Point - offset, reflectionVector), depth + 1, n1, n2, result); } } } private List Trace() { List result = new List(); var lightRays = Light.GetRays(); // var lines = Surface.GetTransformedShape(); foreach (var ray in lightRays) { List rayIntersections = new List(); rayIntersections.Add(ray); TraceRay(ray, 0, 1.0f, 1.5f, rayIntersections); for(int i = 0; i < rayIntersections.Count - 1; ++i) { if(i % 2 == 0) { Gizmos.color = Color.white; } else { Gizmos.color = Color.red; } Gizmos.DrawLine(rayIntersections[i].origin, rayIntersections[i + 1].origin); } Gizmos.color = Color.yellow; Gizmos.DrawLine(rayIntersections[rayIntersections.Count-1].origin, rayIntersections[rayIntersections.Count-1].origin + rayIntersections[rayIntersections.Count - 1].direction * 100); } return result; } private void OnDrawGizmos() { Trace(); } // Update is called once per frame void Update() { } }