/// <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); }
/// <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); } }
/// <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); } }
/// <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); } } } }
/// <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; }
/// <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); }