コード例 #1
0
        /// <summary>
        /// Traces the ray if its inside an object which means its probably in refraction loop
        /// </summary>
        /// <param name="ray"></param>
        /// <param name="obj"></param>
        /// <param name="debug"></param>
        /// <returns></returns>
        protected Vector3 traceRayInside(Ray ray, Primitive obj, bool debug)
        {
            if (ray.isOutside())
            {
                return(TraceRay(ray, debug));                 // Should not happen anyway
            }
            Vector3 ret             = Vector3.Zero;
            float   multiplier      = 1;
            float   absorb_distance = 0;

            // Do the refraction loop. Aka refract, reflect, repeat
            RayHit hit = RayHit.Default();

            while (ray.Depth < Settings.MaxDepth - 1 && multiplier > Constants.EPSILON)
            {
                if (!obj.Intersect(ray, ref hit))
                {
                    return(ret);                              // Should not happen either
                }
                if (debug)
                {
                    DebugData.RefractRays.Add(new Tuple <Ray, RayHit>(ray, hit));
                }

                // Beer absorption
                absorb_distance += hit.T;
                var absorb = QuickMaths.Exp(-obj.Material.Absorb * absorb_distance);

                // Fresnel
                var reflect_multiplier = QuickMaths.Fresnel(obj.Material.RefractionIndex, Constants.LIGHT_IOR, ray.Direction, hit.Normal);
                var refract_multiplier = 1f - reflect_multiplier;

                // refract if its worth
                if (refract_multiplier > Constants.EPSILON)
                {
                    ret += TraceRay(RayTrans.Refract(ray, hit), debug) * refract_multiplier * multiplier * absorb;
                }

                ray         = RayTrans.Reflect(ray, hit);
                multiplier *= reflect_multiplier;
            }

            return(ret);
        }
コード例 #2
0
ファイル: LSL_Api.cs プロジェクト: Gitlab11/opensim
        /// <summary>
        /// Helper to add ray hit in a Tri (triangle).
        /// </summary>
        private void AddRayInTri(Tri triProj, RayTrans rayTrans, ref List<RayHit> rayHits)
        {
            // Check for hit in triangle
            Vector3 posHitProj;
            Vector3 normalProj;
            if (HitRayInTri(triProj, rayTrans.Position1RayProj, rayTrans.VectorRayProj, out posHitProj, out normalProj))
            {
                // Hack to circumvent ghost face bug in PrimMesher by removing hits in (ghost) face plane through shape center
                if (Math.Abs(Vector3.Dot(posHitProj, normalProj)) < m_floatToleranceInCastRay && !rayTrans.ShapeNeedsEnds)
                    return;

                // Transform hit and normal to region coordinate system
                Vector3 posHit = rayTrans.PositionPart + (posHitProj * rayTrans.ScalePart) * rayTrans.RotationPart;
                Vector3 normal = Vector3.Normalize((normalProj * rayTrans.ScalePart) * rayTrans.RotationPart);

                // Remove duplicate hits at triangle intersections
                float distance = Vector3.Distance(rayTrans.Position1Ray, posHit);
                for (int i = rayHits.Count - 1; i >= 0; i--)
                {
                    if (rayHits[i].PartId != rayTrans.PartId)
                        break;
                    if (Math.Abs(rayHits[i].Distance - distance) < m_floatTolerance2InCastRay)
                        return;
                }

                // Build result data set
                RayHit rayHit = new RayHit();
                rayHit.PartId = rayTrans.PartId;
                rayHit.GroupId = rayTrans.GroupId;
                rayHit.Link = rayTrans.Link;
                rayHit.Position = posHit;
                rayHit.Normal = normal;
                rayHit.Distance = distance;
                rayHits.Add(rayHit);
            }
        }
コード例 #3
0
ファイル: LSL_Api.cs プロジェクト: Gitlab11/opensim
 /// <summary>
 /// Helper to parse Tri (triangle) List for ray hits.
 /// </summary>
 private void AddRayInTris(List<Tri> triangles, RayTrans rayTrans, ref List<RayHit> rayHits)
 {
     foreach (Tri triangle in triangles)
     {
         AddRayInTri(triangle, rayTrans, ref rayHits);
     }
 }
コード例 #4
0
ファイル: LSL_Api.cs プロジェクト: Gitlab11/opensim
 /// <summary>
 /// Helper to parse FacetedMesh for ray hits.
 /// </summary>
 private void AddRayInFacetedMesh(FacetedMesh mesh, RayTrans rayTrans, ref List<RayHit> rayHits)
 {
     if (mesh != null)
     {
         foreach (Face face in mesh.Faces)
         {
             for (int i = 0; i < face.Indices.Count; i += 3)
             {
                 Tri triangle = new Tri();
                 triangle.p1 = face.Vertices[face.Indices[i]].Position;
                 triangle.p2 = face.Vertices[face.Indices[i + 1]].Position;
                 triangle.p3 = face.Vertices[face.Indices[i + 2]].Position;
                 AddRayInTri(triangle, rayTrans, ref rayHits);
             }
         }
     }
 }
コード例 #5
0
ファイル: LSL_Api.cs プロジェクト: Gitlab11/opensim
        /// <summary>
        /// Implementation of llCastRay similar to SL 2015-04-21.
        /// http://wiki.secondlife.com/wiki/LlCastRay
        /// Uses pure geometry, bounding shapes, meshing and no physics
        /// for prims, sculpts, meshes, avatars and terrain.
        /// Implements all flags, reject types and data flags.
        /// Can handle both objects/groups and prims/parts, by config.
        /// May sometimes be inaccurate owing to calculation precision,
        /// meshing detail level and a bug in libopenmetaverse PrimMesher.
        /// </summary>
        public LSL_List llCastRayV3(LSL_Vector start, LSL_Vector end, LSL_List options)
        {
            m_host.AddScriptLPS(1);
            LSL_List result = new LSL_List();

            // Prepare throttle data
            int calledMs = Environment.TickCount;
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            UUID regionId = World.RegionInfo.RegionID;
            UUID userId = UUID.Zero;
            int msAvailable = 0;
            // Throttle per owner when attachment or "vehicle" (sat upon)
            if (m_host.ParentGroup.IsAttachment || m_host.ParentGroup.GetSittingAvatars().Count > 0)
            {
                userId = m_host.OwnerID;
                msAvailable = m_msPerAvatarInCastRay;
            }
            // Throttle per parcel when not attachment or vehicle
            else
            {
                LandData land = World.GetLandData(m_host.GetWorldPosition());
                if (land != null)
                    msAvailable = m_msPerRegionInCastRay * land.Area / 65536;
            }
            // Clamp for "oversized" parcels on varregions
            if (msAvailable > m_msMaxInCastRay)
                msAvailable = m_msMaxInCastRay;

            // Check throttle data
            int fromCalledMs = calledMs - m_msThrottleInCastRay;
            lock (m_castRayCalls)
            {
                for (int i = m_castRayCalls.Count - 1; i >= 0; i--)
                {
                    // Delete old calls from throttle data
                    if (m_castRayCalls[i].CalledMs < fromCalledMs)
                        m_castRayCalls.RemoveAt(i);
                    // Use current region (in multi-region sims)
                    else if (m_castRayCalls[i].RegionId == regionId)
                    {
                        // Reduce available time with recent calls
                        if (m_castRayCalls[i].UserId == userId)
                            msAvailable -= m_castRayCalls[i].UsedMs;
                    }
                }
            }

            // Return failure if not enough available time
            if (msAvailable < m_msMinInCastRay)
            {
                result.Add(new LSL_Integer(ScriptBaseClass.RCERR_CAST_TIME_EXCEEDED));
                return result;
            }

            // Initialize
            List<RayHit> rayHits = new List<RayHit>();
            float tol = m_floatToleranceInCastRay;
            Vector3 pos1Ray = start;
            Vector3 pos2Ray = end;

            // Get input options
            int rejectTypes = 0;
            int dataFlags = 0;
            int maxHits = 1;
            bool detectPhantom = false;
            for (int i = 0; i < options.Length; i += 2)
            {
                if (options.GetLSLIntegerItem(i) == ScriptBaseClass.RC_REJECT_TYPES)
                    rejectTypes = options.GetLSLIntegerItem(i + 1);
                else if (options.GetLSLIntegerItem(i) == ScriptBaseClass.RC_DATA_FLAGS)
                    dataFlags = options.GetLSLIntegerItem(i + 1);
                else if (options.GetLSLIntegerItem(i) == ScriptBaseClass.RC_MAX_HITS)
                    maxHits = options.GetLSLIntegerItem(i + 1);
                else if (options.GetLSLIntegerItem(i) == ScriptBaseClass.RC_DETECT_PHANTOM)
                    detectPhantom = (options.GetLSLIntegerItem(i + 1) != 0);
            }
            if (maxHits > m_maxHitsInCastRay)
                maxHits = m_maxHitsInCastRay;
            bool rejectAgents = ((rejectTypes & ScriptBaseClass.RC_REJECT_AGENTS) != 0);
            bool rejectPhysical = ((rejectTypes & ScriptBaseClass.RC_REJECT_PHYSICAL) != 0);
            bool rejectNonphysical = ((rejectTypes & ScriptBaseClass.RC_REJECT_NONPHYSICAL) != 0);
            bool rejectLand = ((rejectTypes & ScriptBaseClass.RC_REJECT_LAND) != 0);
            bool getNormal = ((dataFlags & ScriptBaseClass.RC_GET_NORMAL) != 0);
            bool getRootKey = ((dataFlags & ScriptBaseClass.RC_GET_ROOT_KEY) != 0);
            bool getLinkNum = ((dataFlags & ScriptBaseClass.RC_GET_LINK_NUM) != 0);

            // Calculate some basic parameters
            Vector3 vecRay = pos2Ray - pos1Ray;
            float rayLength = vecRay.Length();

            // Try to get a mesher and return failure if none, degenerate ray, or max 0 hits
            IRendering primMesher = null;
            List<string> renderers = RenderingLoader.ListRenderers(Util.ExecutingDirectory());
            if (renderers.Count < 1 || rayLength < tol || m_maxHitsInCastRay < 1)
            {
                result.Add(new LSL_Integer(ScriptBaseClass.RCERR_UNKNOWN));
                return result;
            }
            primMesher = RenderingLoader.LoadRenderer(renderers[0]);

            // Iterate over all objects/groups and prims/parts in region
            World.ForEachSOG(
                delegate(SceneObjectGroup group)
                {
                    // Check group filters unless part filters are configured
                    bool isPhysical = (group.RootPart != null && group.RootPart.PhysActor != null && group.RootPart.PhysActor.IsPhysical);
                    bool isNonphysical = !isPhysical;
                    bool isPhantom = group.IsPhantom || group.IsVolumeDetect;
                    bool isAttachment = group.IsAttachment;
                    bool doGroup = true;
                    if (isPhysical && rejectPhysical)
                        doGroup = false;
                    if (isNonphysical && rejectNonphysical)
                        doGroup = false;
                    if (isPhantom && detectPhantom)
                        doGroup = true;
                    if (m_filterPartsInCastRay)
                        doGroup = true;
                    if (isAttachment && !m_doAttachmentsInCastRay)
                        doGroup = false;
                    // Parse object/group if passed filters
                    if (doGroup)
                    {
                        // Iterate over all prims/parts in object/group
                        foreach(SceneObjectPart part in group.Parts)
                        {
                            // Check part filters if configured
                            if (m_filterPartsInCastRay)
                            {
                                isPhysical = (part.PhysActor != null && part.PhysActor.IsPhysical);
                                isNonphysical = !isPhysical;
                                isPhantom = ((part.Flags & PrimFlags.Phantom) != 0) || (part.VolumeDetectActive);
                                bool doPart = true;
                                if (isPhysical && rejectPhysical)
                                    doPart = false;
                                if (isNonphysical && rejectNonphysical)
                                    doPart = false;
                                if (isPhantom && detectPhantom)
                                    doPart = true;
                                if (!doPart)
                                    continue;
                            }

                            // Parse prim/part and project ray if passed filters
                            Vector3 scalePart = part.Scale;
                            Vector3 posPart = part.GetWorldPosition();
                            Quaternion rotPart = part.GetWorldRotation();
                            Quaternion rotPartInv = Quaternion.Inverse(rotPart);
                            Vector3 pos1RayProj = ((pos1Ray - posPart) * rotPartInv) / scalePart;
                            Vector3 pos2RayProj = ((pos2Ray - posPart) * rotPartInv) / scalePart;

                            // Filter parts by shape bounding boxes
                            Vector3 shapeBoxMax = new Vector3(0.5f, 0.5f, 0.5f);
                            if (!part.Shape.SculptEntry)
                                shapeBoxMax = shapeBoxMax * (new Vector3(m_primSafetyCoeffX, m_primSafetyCoeffY, m_primSafetyCoeffZ));
                            shapeBoxMax = shapeBoxMax + (new Vector3(tol, tol, tol));
                            if (RayIntersectsShapeBox(pos1RayProj, pos2RayProj, shapeBoxMax))
                            {
                                // Prepare data needed to check for ray hits
                                RayTrans rayTrans = new RayTrans();
                                rayTrans.PartId = part.UUID;
                                rayTrans.GroupId = part.ParentGroup.UUID;
                                rayTrans.Link = group.PrimCount > 1 ? part.LinkNum : 0;
                                rayTrans.ScalePart = scalePart;
                                rayTrans.PositionPart = posPart;
                                rayTrans.RotationPart = rotPart;
                                rayTrans.ShapeNeedsEnds = true;
                                rayTrans.Position1Ray = pos1Ray;
                                rayTrans.Position1RayProj = pos1RayProj;
                                rayTrans.VectorRayProj = pos2RayProj - pos1RayProj;

                                // Get detail level depending on type
                                int lod = 0;
                                // Mesh detail level
                                if (part.Shape.SculptEntry && part.Shape.SculptType == (byte)SculptType.Mesh)
                                    lod = (int)m_meshLodInCastRay;
                                // Sculpt detail level
                                else if (part.Shape.SculptEntry && part.Shape.SculptType == (byte)SculptType.Mesh)
                                    lod = (int)m_sculptLodInCastRay;
                                // Shape detail level
                                else if (!part.Shape.SculptEntry)
                                    lod = (int)m_primLodInCastRay;

                                // Try to get cached mesh if configured
                                ulong meshKey = 0;
                                FacetedMesh mesh = null;
                                if (m_useMeshCacheInCastRay)
                                {
                                    meshKey = part.Shape.GetMeshKey(Vector3.One, (float)(4 << lod));
                                    lock (m_cachedMeshes)
                                    {
                                        m_cachedMeshes.TryGetValue(meshKey, out mesh);
                                    }
                                }

                                // Create mesh if no cached mesh
                                if (mesh == null)
                                {
                                    // Make an OMV prim to be able to mesh part
                                    Primitive omvPrim = part.Shape.ToOmvPrimitive(posPart, rotPart);
                                    byte[] sculptAsset = null;
                                    if (omvPrim.Sculpt != null)
                                        sculptAsset = World.AssetService.GetData(omvPrim.Sculpt.SculptTexture.ToString());

                                    // When part is mesh, get mesh
                                    if (omvPrim.Sculpt != null && omvPrim.Sculpt.Type == SculptType.Mesh && sculptAsset != null)
                                    {
                                        AssetMesh meshAsset = new AssetMesh(omvPrim.Sculpt.SculptTexture, sculptAsset);
                                        FacetedMesh.TryDecodeFromAsset(omvPrim, meshAsset, m_meshLodInCastRay, out mesh);
                                        meshAsset = null;
                                    }

                                    // When part is sculpt, create mesh
                                    // Quirk: Generated sculpt mesh is about 2.8% smaller in X and Y than visual sculpt.
                                    else if (omvPrim.Sculpt != null && omvPrim.Sculpt.Type != SculptType.Mesh && sculptAsset != null)
                                    {
                                        IJ2KDecoder imgDecoder = World.RequestModuleInterface<IJ2KDecoder>();
                                        if (imgDecoder != null)
                                        {
                                            Image sculpt = imgDecoder.DecodeToImage(sculptAsset);
                                            if (sculpt != null)
                                            {
                                                mesh = primMesher.GenerateFacetedSculptMesh(omvPrim, (Bitmap)sculpt, m_sculptLodInCastRay);
                                                sculpt.Dispose();
                                            }
                                        }
                                   }

                                    // When part is shape, create mesh
                                    else if (omvPrim.Sculpt == null)
                                    {
                                        if (
                                            omvPrim.PrimData.PathBegin == 0.0 && omvPrim.PrimData.PathEnd == 1.0 &&
                                            omvPrim.PrimData.PathTaperX == 0.0 && omvPrim.PrimData.PathTaperY == 0.0 &&
                                            omvPrim.PrimData.PathSkew == 0.0 &&
                                            omvPrim.PrimData.PathTwist - omvPrim.PrimData.PathTwistBegin == 0.0
                                        )
                                            rayTrans.ShapeNeedsEnds = false;
                                        mesh = primMesher.GenerateFacetedMesh(omvPrim, m_primLodInCastRay);
                                    }

                                    // Cache mesh if configured
                                    if (m_useMeshCacheInCastRay && mesh != null)
                                    {
                                        lock(m_cachedMeshes)
                                        {
                                            if (!m_cachedMeshes.ContainsKey(meshKey))
                                                m_cachedMeshes.Add(meshKey, mesh);
                                        }
                                    }
                                }
                                // Check mesh for ray hits
                                AddRayInFacetedMesh(mesh, rayTrans, ref rayHits);
                                mesh = null;
                            }
                        }
                    }
                }
            );

            // Check avatar filter
            if (!rejectAgents)
            {
                // Iterate over all avatars in region
                World.ForEachRootScenePresence(
                    delegate (ScenePresence sp)
                    {
                        // Get bounding box
                        Vector3 lower;
                        Vector3 upper;
                        BoundingBoxOfScenePresence(sp, out lower, out upper);
                        // Parse avatar
                        Vector3 scalePart = upper - lower;
                        Vector3 posPart = sp.AbsolutePosition;
                        Quaternion rotPart = sp.GetWorldRotation();
                        Quaternion rotPartInv = Quaternion.Inverse(rotPart);
                        posPart = posPart + (lower + upper) * 0.5f * rotPart;
                        // Project ray
                        Vector3 pos1RayProj = ((pos1Ray - posPart) * rotPartInv) / scalePart;
                        Vector3 pos2RayProj = ((pos2Ray - posPart) * rotPartInv) / scalePart;

                        // Filter avatars by shape bounding boxes
                        Vector3 shapeBoxMax = new Vector3(0.5f + tol, 0.5f + tol, 0.5f + tol);
                        if (RayIntersectsShapeBox(pos1RayProj, pos2RayProj, shapeBoxMax))
                        {
                            // Prepare data needed to check for ray hits
                            RayTrans rayTrans = new RayTrans();
                            rayTrans.PartId = sp.UUID;
                            rayTrans.GroupId = sp.ParentPart != null ? sp.ParentPart.ParentGroup.UUID : sp.UUID;
                            rayTrans.Link = sp.ParentPart != null ? UUID2LinkNumber(sp.ParentPart, sp.UUID) : 0;
                            rayTrans.ScalePart = scalePart;
                            rayTrans.PositionPart = posPart;
                            rayTrans.RotationPart = rotPart;
                            rayTrans.ShapeNeedsEnds = false;
                            rayTrans.Position1Ray = pos1Ray;
                            rayTrans.Position1RayProj = pos1RayProj;
                            rayTrans.VectorRayProj = pos2RayProj - pos1RayProj;

                            // Try to get cached mesh if configured
                            PrimitiveBaseShape prim = PrimitiveBaseShape.CreateSphere();
                            int lod = (int)m_avatarLodInCastRay;
                            ulong meshKey = prim.GetMeshKey(Vector3.One, (float)(4 << lod));
                            FacetedMesh mesh = null;
                            if (m_useMeshCacheInCastRay)
                            {
                                lock (m_cachedMeshes)
                                {
                                    m_cachedMeshes.TryGetValue(meshKey, out mesh);
                                }
                            }

                            // Create mesh if no cached mesh
                            if (mesh == null)
                            {
                                // Make OMV prim and create mesh
                                prim.Scale = scalePart;
                                Primitive omvPrim = prim.ToOmvPrimitive(posPart, rotPart);
                                mesh = primMesher.GenerateFacetedMesh(omvPrim, m_avatarLodInCastRay);

                                // Cache mesh if configured
                                if (m_useMeshCacheInCastRay && mesh != null)
                                {
                                    lock(m_cachedMeshes)
                                    {
                                        if (!m_cachedMeshes.ContainsKey(meshKey))
                                            m_cachedMeshes.Add(meshKey, mesh);
                                    }
                                }
                            }

                            // Check mesh for ray hits
                            AddRayInFacetedMesh(mesh, rayTrans, ref rayHits);
                            mesh = null;
                        }
                    }
                );
            }

            // Check terrain filter
            if (!rejectLand)
            {
                // Parse terrain

                // Mesh terrain and check bounding box
                Vector3 lower;
                Vector3 upper;
                List<Tri> triangles = TrisFromHeightmapUnderRay(pos1Ray, pos2Ray, out lower, out upper);
                lower.Z -= tol;
                upper.Z += tol;
                if ((pos1Ray.Z >= lower.Z || pos2Ray.Z >= lower.Z) && (pos1Ray.Z <= upper.Z || pos2Ray.Z <= upper.Z))
                {
                    // Prepare data needed to check for ray hits
                    RayTrans rayTrans = new RayTrans();
                    rayTrans.PartId = UUID.Zero;
                    rayTrans.GroupId = UUID.Zero;
                    rayTrans.Link = 0;
                    rayTrans.ScalePart = new Vector3 (1.0f, 1.0f, 1.0f);
                    rayTrans.PositionPart = Vector3.Zero;
                    rayTrans.RotationPart = Quaternion.Identity;
                    rayTrans.ShapeNeedsEnds = true;
                    rayTrans.Position1Ray = pos1Ray;
                    rayTrans.Position1RayProj = pos1Ray;
                    rayTrans.VectorRayProj = vecRay;

                    // Check mesh
                    AddRayInTris(triangles, rayTrans, ref rayHits);
                    triangles = null;
                }
            }

            // Sort hits by ascending distance
            rayHits.Sort((s1, s2) => s1.Distance.CompareTo(s2.Distance));

            // Check excess hits per part and group
            for (int t = 0; t < 2; t++)
            {
                int maxHitsPerType = 0;
                UUID id = UUID.Zero;
                if (t == 0)
                    maxHitsPerType = m_maxHitsPerPrimInCastRay;
                else
                    maxHitsPerType = m_maxHitsPerObjectInCastRay;

                // Handle excess hits only when needed
                if (maxHitsPerType < m_maxHitsInCastRay)
                {
                    // Find excess hits
                    Hashtable hits = new Hashtable();
                    for (int i = rayHits.Count - 1; i >= 0; i--)
                    {
                        if (t == 0)
                            id = rayHits[i].PartId;
                        else
                            id = rayHits[i].GroupId;
                        if (hits.ContainsKey(id))
                            hits[id] = (int)hits[id] + 1;
                        else
                            hits[id] = 1;
                    }

                    // Remove excess hits
                    for (int i = rayHits.Count - 1; i >= 0; i--)
                    {
                        if (t == 0)
                            id = rayHits[i].PartId;
                        else
                            id = rayHits[i].GroupId;
                        int hit = (int)hits[id];
                        if (hit > m_maxHitsPerPrimInCastRay)
                        {
                            rayHits.RemoveAt(i);
                            hit--;
                            hits[id] = hit;
                        }
                    }
                }
            }

            // Parse hits into result list according to data flags
            int hitCount = rayHits.Count;
            if (hitCount > maxHits)
                hitCount = maxHits;
            for (int i = 0; i < hitCount; i++)
            {
                RayHit rayHit = rayHits[i];
                if (getRootKey)
                    result.Add(new LSL_Key(rayHit.GroupId.ToString()));
                else
                    result.Add(new LSL_Key(rayHit.PartId.ToString()));
                result.Add(new LSL_Vector(rayHit.Position));
                if (getLinkNum)
                    result.Add(new LSL_Integer(rayHit.Link));
                if (getNormal)
                    result.Add(new LSL_Vector(rayHit.Normal));
            }
            result.Add(new LSL_Integer(hitCount));

            // Add to throttle data
            stopWatch.Stop();
            CastRayCall castRayCall = new CastRayCall();
            castRayCall.RegionId = regionId;
            castRayCall.UserId = userId;
            castRayCall.CalledMs = calledMs;
            castRayCall.UsedMs = (int)stopWatch.ElapsedMilliseconds;
            lock (m_castRayCalls)
            {
                m_castRayCalls.Add(castRayCall);
            }

            // Return hits
            return result;
        }
コード例 #6
0
        /// <summary>
        /// Trace the ray and go around the world in 80ns. Or more :S
        /// </summary>
        /// <param name="ray"></param>
        /// <param name="debug"></param>
        /// <returns></returns>
        public Vector3 TraceRay(Ray ray, bool debug = false)
        {
            Vector3 ret = Vector3.Zero;

            if (ray.Depth > Settings.MaxDepth)
            {
                return(ret);
            }

            // Check for intersections
            RayHit hit = intersect(ray);

            if (hit.Obj == null)
            {
                return(World.Environent == null ? Vector3.Zero : World.Environent.GetColor(ray));
            }
            if (debug)
            {
                DebugData.PrimaryRays.Add(new Tuple <Ray, RayHit>(ray, hit));
            }

            // Calculate color and specular highlights. Pure mirrors dont have diffuse
            Vector3 specular = Vector3.Zero;
            Vector3 color    = Vector3.Zero;

            if (!hit.Obj.Material.IsMirror)
            {
                color += illuminate(ray, hit, ref specular, debug);
                color += World.Environent.AmbientLight;
            }


            // Different materials are handled differently. Would be cool to move that into material
            if (hit.Obj.Material.IsMirror)
            {
                ret = TraceRay(RayTrans.Reflect(ray, hit), debug);
            }
            else if (hit.Obj.Material.IsDielectic)
            {
                var   n1 = ray.isOutside() ? Constants.LIGHT_IOR : hit.Obj.Material.RefractionIndex; // TODO: shorten this shizzle into a func
                var   n2 = ray.isOutside() ? hit.Obj.Material.RefractionIndex : Constants.LIGHT_IOR;
                float reflect_multiplier = QuickMaths.Fresnel(n1, n2, hit.Normal, ray.Direction);
                reflect_multiplier = hit.Obj.Material.Reflectivity + (1f - hit.Obj.Material.Reflectivity) * reflect_multiplier;
                float transmission_multiplier = 1 - reflect_multiplier;

                // Reflect if its worth it
                if (reflect_multiplier > Constants.EPSILON)
                {
                    Ray  reflRay     = RayTrans.Reflect(ray, hit);
                    uint takeSamples = 0;
                    if (hit.Obj.Material.Roughness > 0.001f && ray.Refldepth <= 1)
                    {
                        takeSamples = Settings.MaxReflectionSamples;
                    }
                    else
                    {
                        takeSamples = 1;
                    }
                    //Take many samples for rough reflections
                    for (int i = 0; i < takeSamples; i++)
                    {
                        Ray localRay = reflRay;
                        localRay.Direction = RRandom.RandomChange(reflRay.Direction, hit.Obj.Material.Roughness);
                        ret += reflect_multiplier * TraceRay(localRay, debug);
                    }
                    ret /= takeSamples;
                }
                if (transmission_multiplier > Constants.EPSILON)
                {
                    if (hit.Obj.Material.IsRefractive)
                    {
                        var tmp_ray = RayTrans.Refract(ray, hit);
                        if (!float.IsNaN(tmp_ray.Direction.X)) // Happens rarely
                        {
                            ret += transmission_multiplier * traceRayInside(tmp_ray, hit.Obj, debug);
                        }
                    }
                    else
                    {
                        ret += transmission_multiplier * color;
                    }
                }
            }
            else
            {
                // Standard diffuse
                ret = color;
            }

            return(ret + specular);
        }