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.