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