diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
index e20e4c4594..cf1bd2bbe4 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
@@ -218,6 +218,19 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
protected float m_lABB2SitZ0 = -0.25f;
protected float m_lABB2SitZ1 = 0.25f;
+ protected float m_primSafetyCoeffX = 2.414214f;
+ protected float m_primSafetyCoeffY = 2.414214f;
+ protected float m_primSafetyCoeffZ = 1.618034f;
+ protected float m_floatToleranceInCastRay = 0.000001f;
+ protected float m_floatTolerance2InCastRay = 0.0001f;
+ protected int m_maxHitsInCastRay = 16;
+ protected int m_maxHitsPerPrimInCastRay = 16;
+ protected int m_maxHitsPerObjectInCastRay = 16;
+ protected bool m_detectExitsInCastRay = false;
+ protected bool m_filterPartsInCastRay = false;
+ protected bool m_doAttachmentsInCastRay = false;
+ protected bool m_useCastRayV1 = true;
+
//An array of HTTP/1.1 headers that are not allowed to be used
//as custom headers by llHTTPRequest.
private string[] HttpStandardHeaders =
@@ -320,6 +333,18 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
m_lABB1SitZ1 = lslConfig.GetFloat("LowerAvatarBoundingBoxSittingZcoeff", m_lABB1SitZ1);
m_lABB2SitZ0 = lslConfig.GetFloat("UpperAvatarBoundingBoxSittingZconst", m_lABB2SitZ0);
m_lABB2SitZ1 = lslConfig.GetFloat("UpperAvatarBoundingBoxSittingZcoeff", m_lABB2SitZ1);
+ m_primSafetyCoeffX = lslConfig.GetFloat("PrimBoundingBoxSafetyCoefficientX", m_primSafetyCoeffX);
+ m_primSafetyCoeffY = lslConfig.GetFloat("PrimBoundingBoxSafetyCoefficientY", m_primSafetyCoeffY);
+ m_primSafetyCoeffZ = lslConfig.GetFloat("PrimBoundingBoxSafetyCoefficientZ", m_primSafetyCoeffZ);
+ m_floatToleranceInCastRay = lslConfig.GetFloat("FloatToleranceInLlCastRay", m_floatToleranceInCastRay);
+ m_floatTolerance2InCastRay = lslConfig.GetFloat("FloatTolerance2InLlCastRay", m_floatTolerance2InCastRay);
+ m_maxHitsInCastRay = lslConfig.GetInt("MaxHitsInLlCastRay", m_maxHitsInCastRay);
+ m_maxHitsPerPrimInCastRay = lslConfig.GetInt("MaxHitsPerPrimInLlCastRay", m_maxHitsPerPrimInCastRay);
+ m_maxHitsPerObjectInCastRay = lslConfig.GetInt("MaxHitsPerObjectInLlCastRay", m_maxHitsPerObjectInCastRay);
+ m_detectExitsInCastRay = lslConfig.GetBoolean("DetectExitHitsInLlCastRay", m_detectExitsInCastRay);
+ m_filterPartsInCastRay = lslConfig.GetBoolean("FilterPartsInLlCastRay", m_filterPartsInCastRay);
+ m_doAttachmentsInCastRay = lslConfig.GetBoolean("DoAttachmentsInLlCastRay", m_doAttachmentsInCastRay);
+ m_useCastRayV1 = lslConfig.GetBoolean("UseLlCastRayV1", m_useCastRayV1);
}
IConfig smtpConfig = seConfigSource.Configs["SMTP"];
@@ -13738,7 +13763,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
return contacts[0];
}
- public LSL_List llCastRay(LSL_Vector start, LSL_Vector end, LSL_List options)
+ public LSL_List llCastRayV1(LSL_Vector start, LSL_Vector end, LSL_List options)
{
LSL_List list = new LSL_List();
@@ -13929,6 +13954,739 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
return list;
}
+ ///
+ /// Full 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 give poor results with multi-part meshes where "root"
+ /// part doesn't dominate, owing to "guessed" bounding boxes.
+ /// May sometimes be inaccurate owing to calculation precision
+ /// and a bug in libopenmetaverse PrimMesher.
+ ///
+ public LSL_List llCastRay(LSL_Vector start, LSL_Vector end, LSL_List options)
+ {
+ // Use llCastRay v1 if configured
+ if (m_useCastRayV1)
+ return llCastRayV1(start, end, options);
+
+ // Initialize
+ m_host.AddScriptLPS(1);
+ List rayHits = new List();
+ LSL_List result = new LSL_List();
+ float tol = m_floatToleranceInCastRay;
+ float tol2 = m_floatTolerance2InCastRay;
+
+ // 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 ray = end - start;
+ float rayLength = ray.Length();
+
+ // Try to get a mesher and return failure if none or degenerate ray
+ IRendering primMesher = null;
+ List renderers = RenderingLoader.ListRenderers(Util.ExecutingDirectory());
+ if (renderers.Count < 1 || rayLength < tol)
+ {
+ result.Add(new LSL_Integer(ScriptBaseClass.RCERR_UNKNOWN));
+ return result;
+ }
+ primMesher = RenderingLoader.LoadRenderer(renderers[0]);
+
+ // Used to translate and rotate world so ray is along negative Z axis from origo and
+ // calculations mostly simplified to a 2D projecttion on the X-Y plane
+ Vector3 posProj = new Vector3(-start);
+ Quaternion rotProj = Vector3.RotationBetween(ray, new Vector3(0.0f, 0.0f, -1.0f));
+ Quaternion rotBack = Quaternion.Inverse(rotProj);
+
+ // 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 if passed filters
+
+ // Estimate bounding box from size box
+ Vector3 scaleSafe = part.Scale;
+ if (!part.Shape.SculptEntry)
+ scaleSafe = scaleSafe * (new Vector3(m_primSafetyCoeffX, m_primSafetyCoeffY, m_primSafetyCoeffZ));
+
+ // Filter parts by bounding shapes
+ Vector3 posPartRel = part.GetWorldPosition() + posProj;
+ Vector3 posPartProj = posPartRel * rotProj;
+ if (InBoundingShapes(ray, rayLength, scaleSafe, posPartRel, posPartProj, rotProj))
+ {
+ // 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.Scale = part.Scale;
+ rayTrans.PositionPartProj = posPartProj;
+ rayTrans.PositionProj = posProj;
+ rayTrans.RotationPartProj = rotProj * part.GetWorldRotation();
+ rayTrans.RotationBack = rotBack;
+ rayTrans.NeedsEnds = true;
+ rayTrans.RayLength = rayLength;
+ rayTrans.Tolerance = tol;
+ rayTrans.Tolerance2 = tol2;
+
+ // Make an OMV prim to be able to mesh part
+ Primitive omvPrim = part.Shape.ToOmvPrimitive(posPartProj, rayTrans.RotationPartProj);
+ byte[] sculptAsset = null;
+ if (omvPrim.Sculpt != null)
+ sculptAsset = World.AssetService.GetData(omvPrim.Sculpt.SculptTexture.ToString());
+
+ // When part is mesh, get and check mesh
+ if (omvPrim.Sculpt != null && omvPrim.Sculpt.Type == SculptType.Mesh && sculptAsset != null)
+ {
+ AssetMesh meshAsset = new AssetMesh(omvPrim.Sculpt.SculptTexture, sculptAsset);
+ FacetedMesh mesh = null;
+ FacetedMesh.TryDecodeFromAsset(omvPrim, meshAsset, DetailLevel.Highest, out mesh);
+ meshAsset = null;
+ AddRayInFacetedMesh(mesh, rayTrans, ref rayHits);
+ mesh = null;
+ }
+
+ // When part is sculpt, create and check 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();
+ if (imgDecoder != null)
+ {
+ Image sculpt = imgDecoder.DecodeToImage(sculptAsset);
+ if (sculpt != null)
+ {
+ SimpleMesh mesh = primMesher.GenerateSimpleSculptMesh(omvPrim, (Bitmap)sculpt, DetailLevel.Medium);
+ sculpt.Dispose();
+ AddRayInSimpleMesh(mesh, rayTrans, ref rayHits);
+ mesh = null;
+ }
+ }
+ }
+
+ // When part is prim, create and check 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.NeedsEnds = false;
+ SimpleMesh mesh = primMesher.GenerateSimpleMesh(omvPrim, DetailLevel.Medium);
+ AddRayInSimpleMesh(mesh, rayTrans, ref rayHits);
+ mesh = null;
+ }
+
+ }
+ }
+ }
+ }
+ );
+
+ // Check avatar filter
+ if (!rejectAgents)
+ {
+ // Iterate over all avatars in region
+ World.ForEachRootScenePresence(
+ delegate (ScenePresence sp)
+ {
+ // Parse avatar
+
+ // Get bounding box
+ Vector3 lower;
+ Vector3 upper;
+ BoundingBoxOfScenePresence(sp, out lower, out upper);
+ Vector3 scale = upper - lower;
+
+ // Filter avatars by bounding shapes
+ Vector3 posPartRel = sp.AbsolutePosition + posProj + (lower + upper) * 0.5f * sp.Rotation;
+ Vector3 posPartProj = posPartRel * rotProj;
+ if (InBoundingShapes(ray, rayLength, scale, posPartRel, posPartProj, rotProj))
+ {
+ // 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.Scale = scale;
+ rayTrans.PositionPartProj = posPartProj;
+ rayTrans.PositionProj = posProj;
+ rayTrans.RotationPartProj = rotProj * sp.Rotation;
+ rayTrans.RotationBack = rotBack;
+ rayTrans.NeedsEnds = false;
+ rayTrans.RayLength = rayLength;
+ rayTrans.Tolerance = tol;
+ rayTrans.Tolerance2 = tol2;
+
+ // Make OMV prim, create and check mesh
+ Primitive omvPrim = MakeOpenMetaversePrim(scale, posPartProj, rayTrans.RotationPartProj, ScriptBaseClass.PRIM_TYPE_SPHERE);
+ SimpleMesh mesh = primMesher.GenerateSimpleMesh(omvPrim, DetailLevel.Medium);
+ AddRayInSimpleMesh(mesh, rayTrans, ref rayHits);
+ mesh = null;
+ }
+ }
+ );
+ }
+
+ // Check terrain filter
+ if (!rejectLand)
+ {
+ // Parse terrain
+
+ // Mesh terrain and check projected bounding box
+ Vector3 posPartProj = posProj * rotProj;
+ Quaternion rotPartProj = rotProj;
+ Vector3 lower;
+ Vector3 upper;
+ List triangles = TrisFromHeightmapUnderRay(start, end, out lower, out upper);
+ Vector3 lowerBox = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
+ Vector3 upperBox = new Vector3(float.MinValue, float.MinValue, float.MinValue);
+ int dummy = 0;
+ AddBoundingBoxOfSimpleBox(lower, upper, posPartProj, rotPartProj, true, ref lowerBox, ref upperBox, ref dummy);
+ if (lowerBox.X <= tol && lowerBox.Y <= tol && lowerBox.Z <= tol && upperBox.X >= -tol && upperBox.Y >= -tol && upperBox.Z >= -rayLength - tol)
+ {
+ // Prepare data needed to check for ray hits
+ RayTrans rayTrans = new RayTrans();
+ rayTrans.PartId = UUID.Zero;
+ rayTrans.GroupId = UUID.Zero;
+ rayTrans.Link = 0;
+ rayTrans.Scale = new Vector3 (1.0f, 1.0f, 1.0f);
+ rayTrans.PositionPartProj = posPartProj;
+ rayTrans.PositionProj = posProj;
+ rayTrans.RotationPartProj = rotPartProj;
+ rayTrans.RotationBack = rotBack;
+ rayTrans.NeedsEnds = true;
+ rayTrans.RayLength = rayLength;
+ rayTrans.Tolerance = tol;
+ rayTrans.Tolerance2 = tol2;
+
+ // 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));
+ return result;
+ }
+
+ ///
+ /// Struct for transmitting parameters required for finding llCastRay ray hits.
+ ///
+ public struct RayTrans
+ {
+ public UUID PartId;
+ public UUID GroupId;
+ public int Link;
+ public Vector3 Scale;
+ public Vector3 PositionPartProj;
+ public Vector3 PositionProj;
+ public Quaternion RotationPartProj;
+ public Quaternion RotationBack;
+ public bool NeedsEnds;
+ public float RayLength;
+ public float Tolerance;
+ public float Tolerance2;
+ }
+
+ ///
+ /// Struct for llCastRay ray hits.
+ ///
+ public struct RayHit
+ {
+ public UUID PartId;
+ public UUID GroupId;
+ public int Link;
+ public Vector3 Position;
+ public Vector3 Normal;
+ public float Distance;
+ }
+
+ ///
+ /// Helper to parse SimpleMesh for ray hits.
+ ///
+ private void AddRayInSimpleMesh(SimpleMesh mesh, RayTrans rayTrans, ref List rayHits)
+ {
+ if (mesh != null)
+ {
+ for (int i = 0; i < mesh.Indices.Count; i += 3)
+ {
+ Tri triangle = new Tri();
+ triangle.p1 = mesh.Vertices[mesh.Indices[i]].Position;
+ triangle.p2 = mesh.Vertices[mesh.Indices[i + 1]].Position;
+ triangle.p3 = mesh.Vertices[mesh.Indices[i + 2]].Position;
+ AddRayInTri(triangle, rayTrans, ref rayHits);
+ }
+ }
+ }
+
+ ///
+ /// Helper to parse FacetedMesh for ray hits.
+ ///
+ private void AddRayInFacetedMesh(FacetedMesh mesh, RayTrans rayTrans, ref List rayHits)
+ {
+ if (mesh != null)
+ {
+ foreach (Face face in mesh.Faces)
+ {
+ for (int i = 0; i
+ /// Helper to parse Tri (triangle) List for ray hits.
+ ///
+ private void AddRayInTris(List triangles, RayTrans rayTrans, ref List rayHits)
+ {
+ foreach (Tri triangle in triangles)
+ {
+ AddRayInTri(triangle, rayTrans, ref rayHits);
+ }
+ }
+
+ ///
+ /// Helper to add ray hit in a Tri (triangle).
+ ///
+ private void AddRayInTri(Tri triangle, RayTrans rayTrans, ref List rayHits)
+ {
+ // Check for hit in triangle
+ float distance;
+ Vector3 posHit;
+ Vector3 normal;
+ if (HitRayInTri(triangle, rayTrans, out distance, out posHit, out normal))
+ {
+ // Project hit part back to normal coordinate system
+ Vector3 posPart = rayTrans.PositionPartProj * rayTrans.RotationBack - rayTrans.PositionProj;
+ // Hack to circumvent ghost face bug in PrimMesher by removing hits in (ghost) faces plane through shape center
+ if (Math.Abs(Vector3.Dot(posPart, normal) - Vector3.Dot(posHit, normal)) < rayTrans.Tolerance && !rayTrans.NeedsEnds)
+ return;
+ // Remove duplicate hits at triangle edges and intersections
+ for (int i = rayHits.Count - 1; i >= 0; i--)
+ {
+ if (rayHits[i].PartId == rayTrans.PartId && Math.Abs(rayHits[i].Distance - distance) < rayTrans.Tolerance2)
+ 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);
+ }
+ }
+
+ ///
+ /// Helper to find ray hit in a Tri (triangle).
+ ///
+ private bool HitRayInTri(Tri triangle, RayTrans rayTrans, out float distance, out Vector3 posHit, out Vector3 normal)
+ {
+ // Initialize
+ distance = 0.0f;
+ posHit = Vector3.Zero;
+ normal = Vector3.Zero;
+ float tol = rayTrans.Tolerance;
+
+ // Project triangle on X-Y plane
+ Vector3 pos1 = triangle.p1 * rayTrans.Scale * rayTrans.RotationPartProj + rayTrans.PositionPartProj;
+ Vector3 pos2 = triangle.p2 * rayTrans.Scale * rayTrans.RotationPartProj + rayTrans.PositionPartProj;
+ Vector3 pos3 = triangle.p3 * rayTrans.Scale * rayTrans.RotationPartProj + rayTrans.PositionPartProj;
+
+ // Check if ray/origo inside triangle bounding rectangle
+ Vector3 lower = Vector3.Min(pos1, Vector3.Min(pos2, pos3));
+ Vector3 upper = Vector3.Max(pos1, Vector3.Max(pos2, pos3));
+ if (lower.X > tol || lower.Y > tol || lower.Z > tol || upper.X < -tol || upper.Y < -tol || upper.Z < -rayTrans.RayLength - tol)
+ return false;
+
+ // Check if ray/origo inside every edge or reverse "outside" every edge on exit
+ float dist;
+ bool inside = true;
+ bool outside = true;
+ Vector3 vec1 = pos2 - pos1;
+ dist = pos1.X * vec1.Y - pos1.Y * vec1.X;
+ if (dist < -tol)
+ inside = false;
+ if (dist > tol)
+ outside = false;
+ Vector3 vec2 = pos3 - pos2;
+ dist = pos2.X * vec2.Y - pos2.Y * vec2.X;
+ if (dist < -tol)
+ inside = false;
+ if (dist > tol)
+ outside = false;
+ Vector3 vec3 = pos1 - pos3;
+ dist = pos3.X * vec3.Y - pos3.Y * vec3.X;
+ if (dist < -tol)
+ inside = false;
+ if (dist > tol)
+ outside = false;
+
+ // Skip if ray/origo outside
+ if (!inside && !(outside && m_detectExitsInCastRay))
+ return false;
+
+ // Calculate normal
+ Vector3 normalProj = Vector3.Cross(vec1, vec2);
+ float normalLength = normalProj.Length();
+ // Skip if degenerate triangle
+ if (normalLength < tol)
+ return false;
+ normalProj = normalProj / normalLength;
+ // Skip if ray parallell to triangle plane
+ if (Math.Abs(normalProj.Z) < tol)
+ return false;
+
+ // Calculate distance
+ distance = Vector3.Dot(normalProj, pos2) / normalProj.Z * -1.0f;
+ // Skip if outside ray
+ if (distance < -tol || distance > rayTrans.RayLength + tol)
+ return false;
+
+ // Calculate projected hit position
+ Vector3 posHitProj = new Vector3(0.0f, 0.0f, -distance);
+ // Project hit back to normal coordinate system
+ posHit = posHitProj * rayTrans.RotationBack - rayTrans.PositionProj;
+ normal = normalProj * rayTrans.RotationBack;
+ return true;
+ }
+
+ ///
+ /// Helper to parse selected parts of HeightMap into a Tri (triangle) List and calculate bounding box.
+ ///
+ private List TrisFromHeightmapUnderRay(Vector3 posStart, Vector3 posEnd, out Vector3 lower, out Vector3 upper)
+ {
+ // Get bounding X-Y rectangle of terrain under ray
+ lower = Vector3.Min(posStart, posEnd);
+ upper = Vector3.Max(posStart, posEnd);
+ lower.X = (float)Math.Floor(lower.X);
+ lower.Y = (float)Math.Floor(lower.Y);
+ float zLower = float.MaxValue;
+ upper.X = (float)Math.Ceiling(upper.X);
+ upper.Y = (float)Math.Ceiling(upper.Y);
+ float zUpper = float.MinValue;
+
+ // Initialize Tri (triangle) List
+ List triangles = new List();
+
+ // Set parsing lane direction to major ray X-Y axis
+ Vector3 vec = posEnd - posStart;
+ float xAbs = Math.Abs(vec.X);
+ float yAbs = Math.Abs(vec.Y);
+ bool bigX = true;
+ if (yAbs > xAbs)
+ {
+ bigX = false;
+ vec = vec / yAbs;
+ }
+ else if (xAbs > yAbs || xAbs > 0.0f)
+ vec = vec / xAbs;
+ else
+ vec = new Vector3(1.0f, 1.0f, 0.0f);
+
+ // Simplify by start parsing in lower end of lane
+ if ((bigX && vec.X < 0.0f) || (!bigX && vec.Y < 0.0f))
+ {
+ Vector3 posTemp = posStart;
+ posStart = posEnd;
+ posEnd = posTemp;
+ vec = vec * -1.0f;
+ }
+
+ // First 1x1 rectangle under ray
+ float xFloorOld = 0.0f;
+ float yFloorOld = 0.0f;
+ Vector3 pos = posStart;
+ float xFloor = (float)Math.Floor(pos.X);
+ float yFloor = (float)Math.Floor(pos.Y);
+ AddTrisFromHeightmap(xFloor, yFloor, ref triangles, ref zLower, ref zUpper);
+
+ // Parse every remaining 1x1 rectangle under ray
+ while (pos != posEnd)
+ {
+ // Next 1x1 rectangle under ray
+ xFloorOld = xFloor;
+ yFloorOld = yFloor;
+ pos = pos + vec;
+
+ // Clip position to 1x1 rectangle border
+ xFloor = (float)Math.Floor(pos.X);
+ yFloor = (float)Math.Floor(pos.Y);
+ if (bigX && pos.X > xFloor)
+ {
+ pos.Y -= vec.Y * (pos.X - xFloor);
+ pos.X = xFloor;
+ }
+ else if (!bigX && pos.Y > yFloor)
+ {
+ pos.X -= vec.X * (pos.Y - yFloor);
+ pos.Y = yFloor;
+ }
+
+ // Last 1x1 rectangle under ray
+ if ((bigX && pos.X >= posEnd.X) || (!bigX && pos.Y >= posEnd.Y))
+ {
+ pos = posEnd;
+ xFloor = (float)Math.Floor(pos.X);
+ yFloor = (float)Math.Floor(pos.Y);
+ }
+
+ // Add new 1x1 rectangle in lane
+ if ((bigX && xFloor != xFloorOld) || (!bigX && yFloor != yFloorOld))
+ AddTrisFromHeightmap(xFloor, yFloor, ref triangles, ref zLower, ref zUpper);
+ // Add last 1x1 rectangle in old lane at lane shift
+ if (bigX && yFloor != yFloorOld)
+ AddTrisFromHeightmap(xFloor, yFloorOld, ref triangles, ref zLower, ref zUpper);
+ if (!bigX && xFloor != xFloorOld)
+ AddTrisFromHeightmap(xFloorOld, yFloor, ref triangles, ref zLower, ref zUpper);
+ }
+
+ // Finalize bounding box Z
+ lower.Z = zLower;
+ upper.Z = zUpper;
+
+ // Done and returning Tri (triangle)List
+ return triangles;
+ }
+
+ ///
+ /// Helper to add HeightMap squares into Tri (triangle) List and adjust bounding box.
+ ///
+ private void AddTrisFromHeightmap(float xPos, float yPos, ref List triangles, ref float zLower, ref float zUpper)
+ {
+ int xInt = (int)xPos;
+ int yInt = (int)yPos;
+
+ // Corner 1 of 1x1 rectangle
+ int x = Util.Clamp(xInt+1, 0, World.Heightmap.Width - 1);
+ int y = Util.Clamp(yInt+1, 0, World.Heightmap.Height - 1);
+ Vector3 pos1 = new Vector3(x, y, (float)World.Heightmap[x, y]);
+ // Adjust bounding box
+ zLower = Math.Min(zLower, pos1.Z);
+ zUpper = Math.Max(zUpper, pos1.Z);
+
+ // Corner 2 of 1x1 rectangle
+ x = Util.Clamp(xInt, 0, World.Heightmap.Width - 1);
+ y = Util.Clamp(yInt+1, 0, World.Heightmap.Height - 1);
+ Vector3 pos2 = new Vector3(x, y, (float)World.Heightmap[x, y]);
+ // Adjust bounding box
+ zLower = Math.Min(zLower, pos1.Z);
+ zUpper = Math.Max(zUpper, pos1.Z);
+
+ // Corner 3 of 1x1 rectangle
+ x = Util.Clamp(xInt, 0, World.Heightmap.Width - 1);
+ y = Util.Clamp(yInt, 0, World.Heightmap.Height - 1);
+ Vector3 pos3 = new Vector3(x, y, (float)World.Heightmap[x, y]);
+ // Adjust bounding box
+ zLower = Math.Min(zLower, pos1.Z);
+ zUpper = Math.Max(zUpper, pos1.Z);
+
+ // Corner 4 of 1x1 rectangle
+ x = Util.Clamp(xInt+1, 0, World.Heightmap.Width - 1);
+ y = Util.Clamp(yInt, 0, World.Heightmap.Height - 1);
+ Vector3 pos4 = new Vector3(x, y, (float)World.Heightmap[x, y]);
+ // Adjust bounding box
+ zLower = Math.Min(zLower, pos1.Z);
+ zUpper = Math.Max(zUpper, pos1.Z);
+
+ // Add triangle 1
+ Tri triangle1 = new Tri();
+ triangle1.p1 = pos1;
+ triangle1.p2 = pos2;
+ triangle1.p3 = pos3;
+ triangles.Add(triangle1);
+
+ // Add triangle 2
+ Tri triangle2 = new Tri();
+ triangle2.p1 = pos3;
+ triangle2.p2 = pos4;
+ triangle2.p3 = pos1;
+ triangles.Add(triangle2);
+ }
+
+ ///
+ /// Helper to check if a ray intersects bounding shapes.
+ ///
+ private bool InBoundingShapes(Vector3 ray, float rayLength, Vector3 scale, Vector3 posPartRel, Vector3 posPartProj, Quaternion rotProj)
+ {
+ float tol = m_floatToleranceInCastRay;
+
+ // Check if ray intersects projected bounding box
+ Vector3 lowerBox = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
+ Vector3 upperBox = new Vector3(float.MinValue, float.MinValue, float.MinValue);
+ int dummy = 0;
+ AddBoundingBoxOfSimpleBox(scale * -0.5f, scale * 0.5f, posPartProj, rotProj, true, ref lowerBox, ref upperBox, ref dummy);
+ if (lowerBox.X > tol || lowerBox.Y > tol || lowerBox.Z > tol || upperBox.X < -tol || upperBox.Y < -tol || upperBox.Z < -rayLength - tol)
+ return false;
+
+ // Passed bounding shape filters, so return true
+ return true;
+ }
+
+ ///
+ /// Helper to get link number for a UUID.
+ ///
+ private int UUID2LinkNumber(SceneObjectPart part, UUID id)
+ {
+ SceneObjectGroup group = part.ParentGroup;
+ if (group != null)
+ {
+ // Parse every link for UUID
+ int linkCount = group.PrimCount + group.GetSittingAvatarsCount();
+ for (int link = linkCount; link > 0; link--)
+ {
+ ISceneEntity entity = GetLinkEntity(part, link);
+ // Return link number if UUID match
+ if (entity != null && entity.UUID == id)
+ return link;
+ }
+ }
+ // Return link number 0 if no links or UUID matches
+ return 0;
+ }
+
public LSL_Integer llManageEstateAccess(int action, string avatar)
{
m_host.AddScriptLPS(1);
diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini
index a5d7f1dffd..d3f183e7d4 100644
--- a/bin/OpenSimDefaults.ini
+++ b/bin/OpenSimDefaults.ini
@@ -1477,6 +1477,44 @@
; Avatar bounding box, upper Z value, coefficient to multiply with avatar height, when sitting
UpperAvatarBoundingBoxSittingZcoeff = 0.25
+ ; Safety coefficient for max bounding box from prim size box X coordinate
+ ; Worst case is twisted and sheared box, 1+sqrt(2)
+ PrimBoundingBoxSafetyCoefficientX = 2.414214
+
+ ; Safety coefficient for max bounding box from prim size box Y coordinate
+ ; Worst case is twisted and sheared box, 1+sqrt(2)
+ PrimBoundingBoxSafetyCoefficientY = 2.414214
+
+ ; Safety coefficient for max bounding box from prim size box Z coordinate
+ ; Worst case is twisted tube, 0.5+sqrt(1.25)
+ PrimBoundingBoxSafetyCoefficientZ = 1.618034
+
+ ; Accepted calculation precision error in calculations in llCastRay
+ FloatToleranceInLlCastRay = 0.000001
+
+ ; Accepted distance difference between duplicate hits in llCastRay
+ FloatTolerance2InLlCastRay = 0.0001
+
+ ; Maximum number of returned hits from llCastRay
+ MaxHitsInLlCastRay = 16
+
+ ; Maximum number of returned hits per prim from llCastRay
+ MaxHitsPerPrimInLlCastRay = 16
+
+ ; Maximum number of returned hits per object from llCastRay
+ MaxHitsPerObjectInLlCastRay = 16
+
+ ; Report ray intersections with surfaces on exits from a prim as hits in llCastRay if true
+ DetectExitHitsInLlCastRay = false
+
+ ; Filter on parts instead of groups in llCastRay if true
+ FilterPartsInLlCastRay = false
+
+ ; Detect attachments in llCastRay if true
+ DoAttachmentsInLlCastRay = false
+
+ ; Use legacy version 1 of llCastRay if true
+ UseLlCastRayV1 = true
[DataSnapshot]
; The following set of configs pertains to search.