OpenSim.Region.ScriptEngine.Shared.Api.LSL_Api.llCastRayV3 C# (CSharp) Method

llCastRayV3() public method

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.
public llCastRayV3 ( OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3 start, OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3 end, OpenSim.Region.ScriptEngine.Shared.LSL_Types.list options ) : OpenSim.Region.ScriptEngine.Shared.LSL_Types.list
start OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3
end OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3
options OpenSim.Region.ScriptEngine.Shared.LSL_Types.list
return OpenSim.Region.ScriptEngine.Shared.LSL_Types.list
        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.GetSittingAvatarsCount() > 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 notdetectPhantom = true;
            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)
                    notdetectPhantom = (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)
                {
                    if(group.IsDeleted || group.RootPart == null)
                        return;
                    // Check group filters unless part filters are configured
                    bool isPhysical = (group.RootPart.PhysActor != null && group.RootPart.PhysActor.IsPhysical);
                    bool isNonphysical = !isPhysical;
                    bool isPhantom = group.IsPhantom || group.IsVolumeDetect;
                    bool isAttachment = group.IsAttachment;
                    if (isPhysical && rejectPhysical)
                        return;
                    if (isNonphysical && rejectNonphysical)
                        return;
                    if (isPhantom && notdetectPhantom)
                        return;
                    if (isAttachment && !m_doAttachmentsInCastRay)
                        return;

                    // Parse object/group if passed filters
                    // Iterate over all prims/parts in object/group
                    foreach(SceneObjectPart part in group.Parts)
                    {
                        // ignore PhysicsShapeType.None as physics engines do
                        // or we will get into trouble in future
                        if(part.PhysicsShapeType == (byte)PhysicsShapeType.None)
                            continue;
                        isPhysical = (part.PhysActor != null && part.PhysActor.IsPhysical);
                        isNonphysical = !isPhysical;
                        isPhantom = ((part.Flags & PrimFlags.Phantom) != 0) ||
                                            (part.VolumeDetectActive);
                                                
                        if (isPhysical && rejectPhysical)
                            continue;
                        if (isNonphysical && rejectNonphysical)
                            continue;
                        if (isPhantom && notdetectPhantom)
                            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();
            lock (m_castRayCalls)
            {
                CastRayCall castRayCall = new CastRayCall();
                castRayCall.RegionId = regionId;
                castRayCall.UserId = userId;
                castRayCall.CalledMs = calledMs;
                castRayCall.UsedMs = (int)stopWatch.ElapsedMilliseconds;
                m_castRayCalls.Add(castRayCall);
            }

            // Return hits
            return result;
        }
LSL_Api