public bool RayIntersection(Ray ray, out int x, out int z) { // generate a bounding box for the heightmap in its local space AxisAlignedBox axisAlignedBox = new AxisAlignedBox(new Vector3(0, minHeight, 0), new Vector3(width, maxHeight, height)); Vector3 rayLoc = new Vector3((ray.Origin.x - offsetX) / xzScale, ray.Origin.y / yScale, (ray.Origin.z - offsetZ) / xzScale); Vector3 rayDir = new Vector3(ray.Direction.x / xzScale, ray.Direction.y / yScale, ray.Direction.z / xzScale); rayDir.Normalize(); // convert the ray to local heightmap space Ray tmpRay = new Ray(rayLoc, rayDir); // see if the ray intersects with the bounding box IntersectResult result = tmpRay.Intersects(axisAlignedBox); if (result.Hit) { // move rayLoc up to just before the point of intersection rayLoc = rayLoc + rayDir * ( result.Distance - 1 ); // // deal with edge case where ray is coming from outside the heightmap // and is very near edge of map at intersection. // int insideCounter = 20; while ((!Inside(rayLoc)) && (insideCounter > 0)) { rayLoc += ( rayDir * 0.1f ); insideCounter--; } if (insideCounter == 0) { x = 0; z = 0; return false; } x = (int)Math.Round(rayLoc.x); z = (int)Math.Round(rayLoc.z); if (x < 0) { x = 0; } if (x >= width) { x = width - 1; } if (z < 0) { z = 0; } if (z >= height) { z = height - 1; } bool above = rayLoc.y > heightData[x + z * width]; while (Inside(rayLoc)) { // increment the ray rayLoc += rayDir; x = (int)Math.Round(rayLoc.x); z = (int)Math.Round(rayLoc.z); if (x < 0) { x = 0; } if (x >= width) { x = width - 1; } if (z < 0) { z = 0; } if (z >= height) { z = height - 1; } if (above != (rayLoc.y > heightData[x + z * width])) { // we found a hit return true; } } } x = 0; z = 0; return false; }
// By the time this method is called, we know for sure that // the object has collision volumes. protected float CheckCollisionWithCollisionShapes(ObjectNode objNode, Ray ray) { float distance = float.MaxValue; if (objNode.Collider != null) { MovingObject mo = objNode.Collider; foreach (MovingPart part in mo.parts) { CollisionShape partShape = mo.parts[0].shape; // Check for intersection of a line 1000 meters long from the ray origin toward the ray direction float distanceToShape = partShape.RayIntersectionDistance(ray.Origin, ray.Origin + ray.Direction * (1000f * OneMeter)); if (distanceToShape == float.MaxValue) continue; else if (distanceToShape < distance) distance = distanceToShape; } } else if (objNode.CollisionShapes != null) { foreach (CollisionShape shape in objNode.CollisionShapes) { // Check for intersection of a line 1000 meters long from the ray origin toward the ray direction float distanceToShape = shape.RayIntersectionDistance(ray.Origin, ray.Origin + ray.Direction * (1000f * OneMeter)); if (distanceToShape == float.MaxValue) continue; else if (distanceToShape < distance) { // log.DebugFormat("Client.CheckCollisionWithCollisionShapes: objNode {0}, distanceToShape {1}, shape {2}", // objNode.Name,distanceToShape, shape); distance = distanceToShape; } } } return distance; }
protected bool CheckCollision(Ray ray, SceneObject sceneObj, out float t) { t = float.MaxValue; if (!(sceneObj is Entity)) return false; bool intersect = false; Entity entity = (Entity)sceneObj; VertexData vData; if (entity.Mesh.HasSkeleton) { // The blended vertex buffers have already been transformed for (int i = 0; i < entity.SubEntityCount; ++i) { SubEntity subEntity = entity.GetSubEntity(i); if (!subEntity.IsVisible) continue; if (subEntity.SubMesh.useSharedVertices) vData = entity.SharedBlendedVertexData; else vData = subEntity.BlendedVertexData; IndexData iData = subEntity.SubMesh.indexData; if (CheckCollision(vData, iData, ray.Origin, ray.Direction, ref t)) intersect = true; } } else { // Transform the ray based on the inverse of the sceneObj transform, so // we don't need to transform each point in the submesh. Matrix4 inverseTransform = sceneObj.ParentNodeFullTransform.Inverse(); Vector3 origin = inverseTransform * ray.Origin; Vector3 direction = (inverseTransform * (ray.Origin + ray.Direction)) - origin; for (int i = 0; i < entity.SubEntityCount; ++i) { SubEntity subEntity = entity.GetSubEntity(i); if (!subEntity.IsVisible) continue; if (subEntity.SubMesh.useSharedVertices) vData = entity.Mesh.SharedVertexData; else vData = subEntity.SubMesh.vertexData; IndexData iData = subEntity.SubMesh.indexData; if (CheckCollision(vData, iData, origin, direction, ref t)) intersect = true; } } return intersect; }
public Vector3 PickTerrain(Ray pointRay) { Axiom.Core.RaySceneQuery q = scene.CreateRayQuery(pointRay); q.QueryMask = (ulong)Axiom.SceneManagers.Multiverse.RaySceneQueryType.FirstTerrain; List<RaySceneQueryResultEntry> results = q.Execute(); if (results.Count > 0) { RaySceneQueryResultEntry result = results[0]; return (result.worldFragment.SingleIntersection); } else { return Vector3.Zero; } }
/// <summary> /// This overloading permits the user to pass in a query mask /// mouseX and mouseY are in terms of Overlay coordinates /// (0 to 1 starting from the upper left corner of the screen). /// </summary> /// <param name="mouseX"></param> /// <param name="mouseY"></param> /// <param name="queryMas"></param> /// <returns></returns> public ObjectNode CastRay(float mouseX, float mouseY, ulong queryMask) { Ray ray = camera.GetCameraToViewportRay(mouseX, mouseY); #if NOT Vector3 origin, direction; float winWidth = (float)(2 * camera.Near * Math.Tan(MathUtil.DegreesToRadians(camera.FOV / 2))); direction.x = (mouseX - .5f) * winWidth; direction.y = (.5f - mouseY) * winWidth / camera.AspectRatio; direction.z = -camera.Near; origin = camera.Position; direction = camera.Orientation * direction; Ray ray = new Ray(origin, direction); #endif RaySceneQuery query = scene.CreateRayQuery(ray, queryMask); query.SortByDistance = true; List<RaySceneQueryResultEntry> results = query.Execute(); if (results.Count == 0) return null; MovableObject closestObj = null; float closestMatch = float.MaxValue; // Check that the picked object is not terrain, not a prop, and // that it is the closest object. foreach (RaySceneQueryResultEntry result in results) { MovableObject sceneObj = result.SceneObject; if (sceneObj == null || sceneObj.UserData == null || !(sceneObj.UserData is ObjectNode)) continue; // Skip player and buildings (props) ObjectNode objNode = (ObjectNode)sceneObj.UserData; float distanceToObject = result.Distance; bool hitCollisionVolume = false; if (targetBasedOnCollisionVolumes) { // If there are are no collision shapes to collide // with, fall back to the distance to the bounding // box. if ((objNode.Collider != null && objNode.Collider.parts.Count > 0) || (objNode.CollisionShapes != null && objNode.CollisionShapes.Count > 0)) { float previousDistance = distanceToObject; distanceToObject = CheckCollisionWithCollisionShapes(objNode, ray); hitCollisionVolume = true; // log.DebugFormat("Client.CastRay: objNode {0}, distanceToObject {1}, previousDistance {2}", // objNode.Name, distanceToObject, previousDistance); } } float dist; #if PER_POLY_PICKING if (!CheckCollision(ray, sceneObj, out dist)) continue; #else // If we aren't doing the per-poly stuff, we would use this instead. dist = distanceToObject; #endif if (dist < closestMatch) { // If the closest thing is a collision volume, and // it's not targetable and not the player, set // closestObj to null. if (!objNode.Targetable && objNode != worldManager.Player && hitCollisionVolume) { closestMatch = dist; closestObj = null; } else if (objNode.Targetable) { closestMatch = dist; closestObj = sceneObj; } } } if (closestObj == null) return null; else return closestObj.UserData as ObjectNode; }
/// <summary> /// <see cref="RaySceneQuery"/> /// </summary> public void Execute(IRaySceneQueryListener listener, Camera camera) { clearFragmentList(); // if the world is not initialized, then just quit out now with no hits if (!TerrainManager.Instance.Initialized) { return; } ulong mask = QueryMask; SceneQuery.WorldFragment frag; bool terrainQuery = (mask & (ulong)RaySceneQueryType.AllTerrain) != 0 || (mask & (ulong)RaySceneQueryType.FirstTerrain) != 0; // if the query is a terrain query that is exactly vertical, then force it into the "Height" fastpath if (((mask & (ulong)RaySceneQueryType.Height) != 0) || (terrainQuery && (this.Ray.Direction.x == 0.0f) && (this.Ray.Direction.z == 0.0f))) { // we don't want to bother checking for entities because a // UNIT_Y ray is assumed to be a height test, not a ray test frag = new SceneQuery.WorldFragment(); fragmentList.Add(frag); frag.FragmentType = WorldFragmentType.SingleIntersection; Vector3 origin = this.Ray.Origin; origin.y = 0; // ensure that it's within bounds frag.SingleIntersection = getHeightAt(origin); listener.OnQueryResult(frag, Math.Abs(frag.SingleIntersection.y - this.Ray.Origin.y)); } else { // Check for all entity contacts if ((mask & (ulong)RaySceneQueryType.Entities) != 0) { base.Execute(listener); } // Check for contact with the closest entity triangle // or all entity triangles. Ignores entities that // don't have a TriangleIntersector associated with // their meshes. bool firstTriangleQuery = (mask & (ulong)(RaySceneQueryType.FirstEntityTriangle)) != 0; bool allTrianglesQuery = (mask & (ulong)(RaySceneQueryType.AllEntityTriangles)) != 0; if (firstTriangleQuery | allTrianglesQuery) { rayOrigin = this.ray.Origin; // Start by getting the entities whose bounding // boxes intersect the ray. If there are none, // we're done. List<MovableObject> candidates = new List<MovableObject>(); foreach (Dictionary<string, MovableObject> objectMap in creator.MovableObjectMaps) { foreach (MovableObject obj in objectMap.Values) { // skip if unattached or filtered out by query flags if (!obj.IsAttached || (obj.QueryFlags & queryMask) == 0) continue; // test the intersection against the world bounding box of the entity IntersectResult results = MathUtil.Intersects(ray, obj.GetWorldBoundingBox()); if (results.Hit) candidates.Add(obj); } } // Get the camera.Near value Camera cam = camera; float nearDistance = cam.Near; float closestDistance = float.MaxValue; Vector3 closestIntersection = Vector3.Zero; Entity closestEntity = null; List<EntityAndIntersection> allEntities = new List<EntityAndIntersection>(); foreach (MovableObject obj in candidates) { // skip if unattached or filtered out by query flags if (!obj.IsAttached || (obj.QueryFlags & queryMask) == 0) continue; Entity entity = obj as Entity; if (entity == null) continue; // skip if its mesh doesn't have triangles if (entity.Mesh == null || entity.Mesh.TriangleIntersector == null) continue; // transform the ray to the space of the mesh Matrix4 inverseTransform = entity.ParentNodeFullTransform.Inverse(); Matrix4 inverseWithoutTranslation = inverseTransform; inverseWithoutTranslation.Translation = Vector3.Zero; Vector3 transformedOrigin = inverseTransform * ray.Origin; Ray transformedRay = new Ray(transformedOrigin, (inverseWithoutTranslation * ray.Direction).ToNormalized()); // test the intersection against the world bounding box of the entity Vector3 untransformedIntersection; if (entity.Mesh.TriangleIntersector.ClosestRayIntersection(transformedRay, Vector3.Zero, nearDistance, out untransformedIntersection)) { Vector3 intersection = entity.ParentNodeFullTransform * untransformedIntersection; if (allTrianglesQuery) allEntities.Add(new EntityAndIntersection(entity, intersection)); float distance = (ray.Origin - intersection).Length; if (firstTriangleQuery && distance < closestDistance) { closestDistance = distance; closestEntity = entity; closestIntersection = intersection; } } } if (firstTriangleQuery && closestEntity != null) { frag = new SceneQuery.WorldFragment(); fragmentList.Add(frag); frag.FragmentType = WorldFragmentType.SingleIntersection; frag.SingleIntersection = closestIntersection; listener.OnQueryResult(frag, closestDistance); } else if (allTrianglesQuery && allEntities.Count > 0) { allEntities.Sort(distanceToCameraCompare); foreach (EntityAndIntersection ei in allEntities) listener.OnQueryResult(ei.entity, (rayOrigin - ei.intersection).Length); } } if (terrainQuery) { Vector3 ray = Ray.Origin; Vector3 land = getHeightAt(ray); float dist = 0, resFactor = TerrainManager.oneMeter; // find the larger of x and z directions of the ray direction float maxXZ = Math.Max(Math.Abs(Ray.Direction.x), Math.Abs(Ray.Direction.z)); // Only bother if the non-default mask has been set if ((mask & (ulong)RaySceneQueryType.OnexRes) != 0) { if ((mask & (ulong)RaySceneQueryType.TwoxRes) != 0) { resFactor = TerrainManager.oneMeter / 2; } else if ((mask & (ulong)RaySceneQueryType.FourxRes) != 0) { resFactor = TerrainManager.oneMeter / 4; } else if ((mask & (ulong)RaySceneQueryType.EightxRes) != 0) { resFactor = TerrainManager.oneMeter / 8; } } // this scales the res factor so that we move along the ray by a distance that results // in shift of one meter along either the X or Z axis (whichever is longer) resFactor = resFactor / maxXZ; SubPageHeightMap sp; // bool east = false; (unused) // bool south = false; (unused) // if ( Ray.Origin.x > 0 ) // { // east = true; // } // if ( Ray.Origin.z > 0 ) // { // south = true; // } ray = Ray.Origin; sp = TerrainManager.Instance.LookupSubPage(ray); while (sp != null) { SubPageHeightMap newsp; AxisAlignedBox tileBounds = sp.BoundingBox; IntersectResult intersect = MathUtil.Intersects(Ray, tileBounds); if (intersect.Hit) { // step through this tile while ((newsp = TerrainManager.Instance.LookupSubPage(RoundRay(ray))) == sp) { land = getHeightAt(ray); if (ray.y < land.y) { frag = new SceneQuery.WorldFragment(); fragmentList.Add(frag); frag.FragmentType = WorldFragmentType.SingleIntersection; frag.SingleIntersection = land; listener.OnQueryResult(frag, dist); if ((mask & (ulong)RaySceneQueryType.FirstTerrain) != 0) { return; } } ray += Ray.Direction * resFactor; dist += 1 * resFactor; } // if we fall off the end of the above loop without getting a hit, then the hit should be // right at the far edge of the tile, so handle that case. land = getHeightAt(ray); if (ray.y < land.y) { frag = new SceneQuery.WorldFragment(); fragmentList.Add(frag); frag.FragmentType = WorldFragmentType.SingleIntersection; frag.SingleIntersection = land; listener.OnQueryResult(frag, dist); //LogManager.Instance.Write("MVSM:RaySceneQuery:End of tile ray collision"); if ((mask & (ulong)RaySceneQueryType.FirstTerrain) != 0) { return; } } else { //LogManager.Instance.Write("MVSM:RaySceneQuery:End of tile reached without expected intersection"); } } else { // step over this tile while ((newsp = TerrainManager.Instance.LookupSubPage(RoundRay(ray))) == sp) { // XXX - this is not the most efficient method... ray += Ray.Direction * resFactor; dist += 1 * resFactor; } } sp = newsp; } } } }
public Vector3 ObjectPlacementLocation(Vector3 objPosition) { Ray pointRay = new Ray(new Vector3(objPosition.x, camera.Position.y, objPosition.z), Vector3.NegativeUnitY); Axiom.Core.RaySceneQuery q = scene.CreateRayQuery(pointRay); q.QueryMask = (ulong)Axiom.SceneManagers.Multiverse.RaySceneQueryType.AllEntityTriangles; List<RaySceneQueryResultEntry> results = q.Execute(); if (results.Count > 0) { foreach (RaySceneQueryResultEntry result in results) { IWorldObject obj = DisplayObject.LookupName(result.SceneObject.Name); if (obj is StaticObject && obj.AcceptObjectPlacement) { Vector3 position = pointRay.Origin + (pointRay.Direction * result.Distance); return position; } } } return PickTerrain(pointRay); }
/// <summary> /// Creates a query to return objects found along the ray. /// </summary> /// <param name="ray">Ray to use for the intersection query.</param> /// <returns>A specialized implementation of RaySceneQuery for this scene manager.</returns> public virtual RaySceneQuery CreateRayQuery(Ray ray) { return CreateRayQuery(ray, 0xffffffff); }
/// <summary> /// Creates a query to return objects found along the ray. /// </summary> /// <param name="ray">Ray to use for the intersection query.</param> /// <returns>A specialized implementation of RaySceneQuery for this scene manager.</returns> public virtual RaySceneQuery CreateRayQuery(Ray ray, ulong mask) { DefaultRaySceneQuery query = new DefaultRaySceneQuery(this); query.Ray = ray; query.QueryMask = mask; return query; }
public override Axiom.Core.RaySceneQuery CreateRayQuery(Ray ray) { return CreateRayQuery(ray, defaultRayQueryFlags); }
/// <summary> /// Creates a RaySceneQuery for this scene manager. /// </summary> /// <param name="ray">Details of the ray which describes the region for this query.</param> /// <param name="mask">The query mask to apply to this query; can be used to filter out certain objects; see SceneQuery for details.</param> /// <returns> /// The instance returned from this method must be destroyed by calling /// SceneManager::destroyQuery when it is no longer required. /// </returns> /// <remarks> /// This method creates a new instance of a query object for this scene manager, /// looking for objects which fall along a ray. See SceneQuery and RaySceneQuery /// for full details. /// </remarks> public override Axiom.Core.RaySceneQuery CreateRayQuery(Ray ray, ulong mask) { Axiom.Core.RaySceneQuery q = new Axiom.SceneManagers.Multiverse.RaySceneQuery(this); q.Ray = ray; q.QueryMask = mask; return q; }
/// <summary> /// Creates a RaySceneQuery for this scene manager. /// </summary> /// <remarks> /// This method creates a new instance of a query object for this scene manager, /// looking for objects which fall along a ray. See SceneQuery and RaySceneQuery /// for full details. /// </remarks> /// <param name="ray">Details of the ray which describes the region for this query.</param> /// <param name="mask">The query mask to apply to this query; can be used to filter out certain objects; see SceneQuery for details.</param> public override RaySceneQuery CreateRayQuery(Ray ray, ulong mask) { BspRaySceneQuery q = new BspRaySceneQuery(this); q.Ray = ray; q.QueryMask = mask; return q; }
protected virtual void ProcessNode(BspNode node, Ray tracingRay, float maxDistance, float traceDistance) { // check if ray already encountered a solid brush if (StopRayTracing) return; if (node.IsLeaf) { ProcessLeaf(node, tracingRay, maxDistance, traceDistance); return; } IntersectResult result = tracingRay.Intersects(node.SplittingPlane); if (result.Hit) { if (result.Distance < maxDistance) { if (node.GetSide(tracingRay.Origin) == PlaneSide.Negative) { ProcessNode(node.BackNode, tracingRay, result.Distance, traceDistance); Vector3 splitPoint = tracingRay.Origin + tracingRay.Direction * result.Distance; ProcessNode(node.FrontNode, new Ray(splitPoint, tracingRay.Direction), maxDistance - result.Distance, traceDistance + result.Distance); } else { ProcessNode(node.FrontNode, tracingRay, result.Distance, traceDistance); Vector3 splitPoint = tracingRay.Origin + tracingRay.Direction * result.Distance; ProcessNode(node.BackNode, new Ray(splitPoint, tracingRay.Direction), maxDistance - result.Distance, traceDistance + result.Distance); } } else ProcessNode(node.GetNextNode(tracingRay.Origin), tracingRay, maxDistance, traceDistance); } else ProcessNode(node.GetNextNode(tracingRay.Origin), tracingRay, maxDistance, traceDistance); }
protected virtual void ProcessLeaf(BspNode leaf, Ray tracingRay, float maxDistance, float traceDistance) { MovableObjectCollection objects = leaf.Objects; int numObjects = objects.Count; //Check ray against objects for(int a = 0; a < numObjects; a++) { MovableObject obj = objects[a]; // Skip this object if collision not enabled if((obj.QueryFlags & queryMask) == 0) continue; //Test object as bounding box IntersectResult result = tracingRay.Intersects(obj.GetWorldBoundingBox()); // if the result came back positive and intersection point is inside // the node, fire the event handler if(result.Hit && result.Distance <= maxDistance) { listener.OnQueryResult(obj, result.Distance + traceDistance); } } PlaneBoundedVolume boundedVolume = new PlaneBoundedVolume(PlaneSide.Positive); BspBrush intersectBrush = null; float intersectBrushDist = float.PositiveInfinity; // Check ray against brushes for (int brushPoint=0; brushPoint < leaf.SolidBrushes.Length; brushPoint++) { BspBrush brush = leaf.SolidBrushes[brushPoint]; if (brush == null) continue; boundedVolume.planes = brush.Planes; IntersectResult result = tracingRay.Intersects(boundedVolume); // if the result came back positive and intersection point is inside // the node, check if this brush is closer if(result.Hit && result.Distance <= maxDistance) { if (result.Distance < intersectBrushDist) { intersectBrushDist = result.Distance; intersectBrush = brush; } } } if (intersectBrush != null) { listener.OnQueryResult(intersectBrush.Fragment, intersectBrushDist + traceDistance); StopRayTracing = true; } }