/// <summary> /// Intersects the specified client point with the scene, knowing that the given /// HitRecords were the result of having called Dispatch with these same client points</summary> /// <param name="x">Client x coordinate</param> /// <param name="y">Client y coordinate</param> /// <param name="hits">The hits</param> /// <param name="pt">The intersection point</param> /// <param name="surfaceNormal">The surface normal of the target object at the intersection /// point, or the zero vector if the surface normal could not be found. Check against /// Vec3F.ZeroVector before using.</param> /// <returns>True iff client point intersects scene</returns> protected bool Intersect(int x, int y, HitRecord[] hits, ref Vec3F pt, out Vec3F surfaceNormal) { surfaceNormal = new Vec3F(); if (hits.Length == 0) { return(false); } HitRecord hit = hits[0]; if (hit.HasNormal) { surfaceNormal = hit.Normal; } if (hit.HasWorldIntersection) { pt = hit.WorldIntersection; return(true); } return(false); }
/// <summary> /// Performs actions during control drag</summary> /// <param name="hit">Hit record</param> /// <param name="x">Mouse x position</param> /// <param name="y">Mouse y position</param> /// <param name="action">Render action</param> /// <param name="camera">Camera</param> /// <param name="transform">Transform</param> /// <returns>Translation, in world coordinates</returns> public Vec3F OnDrag(HitRecord hit, float x, float y, IRenderAction action, Camera camera, Matrix4F transform) { float a1, a2; Matrix4F W = new Matrix4F(); W.Mul(transform, camera.ViewMatrix); // Setup rays, in view space. (-z goes into the screen.) Ray3F ray0 = camera.CreateRay(m_iX, m_iY); Ray3F ray = camera.CreateRay(x, y); // Build axis and origin in view space Vec3F xAxis = W.XAxis; Vec3F yAxis = W.YAxis; Vec3F zAxis = W.ZAxis; Vec3F origin = W.Translation; Vec3F trans = new Vec3F(); // Choose the best projection plane according to the projection angle switch ((HitElement)hit.RenderObjectData[1]) { case HitElement.X_ARROW: { a1 = Math.Abs(Vec3F.Dot(ray0.Direction, yAxis)); a2 = Math.Abs(Vec3F.Dot(ray0.Direction, zAxis)); Vec3F axis = (a1 > a2 ? yAxis : zAxis); Vec3F p0 = ray0.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = ray.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot(xAxis, p1 - p0); Vec3F xLocal = transform.XAxis; trans = dragAmount * xLocal; } break; case HitElement.Y_ARROW: { a1 = Math.Abs(Vec3F.Dot(ray0.Direction, zAxis)); a2 = Math.Abs(Vec3F.Dot(ray0.Direction, xAxis)); Vec3F axis = (a1 > a2 ? zAxis : xAxis); Vec3F p0 = ray0.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = ray.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot(yAxis, p1 - p0); Vec3F yLocal = transform.YAxis; trans = dragAmount * yLocal; } break; case HitElement.Z_ARROW: { a1 = Math.Abs(Vec3F.Dot(ray0.Direction, xAxis)); a2 = Math.Abs(Vec3F.Dot(ray0.Direction, yAxis)); Vec3F axis = (a1 > a2 ? xAxis : yAxis); Vec3F p0 = ray0.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); Vec3F p1 = ray.IntersectPlane(axis, -Vec3F.Dot(axis, origin)); float dragAmount = Vec3F.Dot(zAxis, p1 - p0); Vec3F zLocal = transform.ZAxis; trans = dragAmount * zLocal; } break; case HitElement.XY_SQUARE: { Vec3F p0 = ray0.IntersectPlane(zAxis, -Vec3F.Dot(zAxis, origin)); Vec3F p1 = ray.IntersectPlane(zAxis, -Vec3F.Dot(zAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragX = Vec3F.Dot(xAxis, deltaLocal); float dragY = Vec3F.Dot(yAxis, deltaLocal); Vec3F xLocal = transform.XAxis; Vec3F yLocal = transform.YAxis; trans = dragX * xLocal + dragY * yLocal; } break; case HitElement.YZ_SQUARE: { Vec3F p0 = ray0.IntersectPlane(xAxis, -Vec3F.Dot(xAxis, origin)); Vec3F p1 = ray.IntersectPlane(xAxis, -Vec3F.Dot(xAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragY = Vec3F.Dot(yAxis, deltaLocal); float dragZ = Vec3F.Dot(zAxis, deltaLocal); Vec3F yLocal = transform.YAxis; Vec3F zLocal = transform.ZAxis; trans = dragY * yLocal + dragZ * zLocal; } break; case HitElement.XZ_SQUARE: { Vec3F p0 = ray0.IntersectPlane(yAxis, -Vec3F.Dot(yAxis, origin)); Vec3F p1 = ray.IntersectPlane(yAxis, -Vec3F.Dot(yAxis, origin)); Vec3F deltaLocal = p1 - p0; float dragX = Vec3F.Dot(xAxis, deltaLocal); float dragZ = Vec3F.Dot(zAxis, deltaLocal); Vec3F xLocal = transform.XAxis; Vec3F zLocal = transform.ZAxis; trans = dragX * xLocal + dragZ * zLocal; } break; } return(trans); }
/// <summary> /// Returns whether the control was hit during a picking operation</summary> /// <param name="hit">Hit record</param> /// <returns>True if the control was hit during a picking operation</returns> public bool IsHit(HitRecord hit) { return(hit.RenderObjectData[0] == m_nameBase); }
private HitRecord[] PopulateOpenGlSelection(ICollection <TraverseNode> traverseList) { // Ensure that OpenGL is in correct state. double[] viewMat = null; double[] projectionMat = null; int[] viewport = null; if (m_frustumPick == false) { Gl.glViewport(0, 0, m_width, m_height); Gl.glMatrixMode(Gl.GL_PROJECTION); Gl.glLoadIdentity(); Util3D.glMultMatrixf(m_projectionMatrix); Gl.glMatrixMode(Gl.GL_MODELVIEW); Gl.glLoadIdentity(); Util3D.glMultMatrixf(m_viewMatrix); viewMat = new double[16]; Gl.glGetDoublev(Gl.GL_MODELVIEW_MATRIX, viewMat); projectionMat = new double[16]; Gl.glGetDoublev(Gl.GL_PROJECTION_MATRIX, projectionMat); viewport = new int[4]; viewport[0] = viewport[1] = 0; viewport[2] = m_width; viewport[3] = m_height; } // Construct traverse array HitRecord[] selection = null; List <HitRecord> selectionList = new List <HitRecord>(); TraverseNode[] travArray = new TraverseNode[traverseList.Count]; traverseList.CopyTo(travArray, 0); uint start = 0; for (int i = 0; i < m_openGlHits; ++i) { uint nameCount = (uint)m_selectionBuffer[start]; if (nameCount > 0) { uint travNodeIndex = (uint)m_selectionBuffer[start + 3]; TraverseNode travNode = travArray[travNodeIndex]; HitRecord hitRecord; if (m_frustumPick) { hitRecord = new HitRecord( travNode.GraphPath, travNode.RenderObject, new Matrix4F(travNode.Transform), new uint[nameCount - 1]); } else { hitRecord = new HitRecord( travNode.GraphPath, travNode.RenderObject, new Matrix4F(travNode.Transform), new uint[nameCount - 1]); // Transform screen to world and record world-space intersection point. float zMin = ((float)((uint)m_selectionBuffer[start + 1])) / 0xFFFFFFFF; Vec3F intersectionPt = GetWorldIntersectionFromScreen( m_x, m_y, zMin, viewMat, projectionMat, viewport); hitRecord.WorldIntersection = intersectionPt; } // Populate object data for (uint j = 0; j < nameCount - 1; j++) { hitRecord.RenderObjectData[j] = (uint)m_selectionBuffer[start + 4 + j]; } selectionList.Add(hitRecord); start += 3 + nameCount; } } selection = new HitRecord[selectionList.Count]; selectionList.CopyTo(selection, 0); return(selection); }
/// <summary> /// Dispatches untyped items. Replaces DispatchNotTyped(). To get the same behavior as /// the old DispatchNotTyped(), set the TypeFilter property to null prior to calling.</summary> /// <param name="traverseList">The traverse list</param> /// <param name="camera">The camera</param> protected void DispatchTraverseList(ICollection <TraverseNode> traverseList, Camera camera) { // Prepare for geometric picking -- create the ray in world space and reset geometric hit-list. // First create the ray in viewing coordinates and transform to world coordinates. float nx = (m_x / (float)m_width) - 0.5f; //normalized x float ny = 0.5f - (m_y / (float)m_height); //normalized y Ray3F rayWorld = camera.CreateRay(nx, ny); Matrix4F worldToView = camera.ViewMatrix; Matrix4F viewToWorld = new Matrix4F(); viewToWorld.Invert(worldToView); rayWorld.Transform(viewToWorld); ClearHitList(); // for geometric picking. will be cleared for each HitRecord. List <uint> userData = new List <uint>(1); // Dispatch traverse list int index = 0; foreach (TraverseNode node in traverseList) { // First test for filtering. IRenderObject renderObject = node.RenderObject; if (FilterByType(renderObject)) { IIntersectable intersectable = renderObject.GetIntersectable(); IGeometricPick geometricPick = intersectable as IGeometricPick; if (geometricPick != null) { // Picking by geometry. Matrix4F objToWorld = new Matrix4F(node.Transform); Matrix4F worldToObj = new Matrix4F(); worldToObj.Invert(objToWorld); Matrix4F viewToObj = Matrix4F.Multiply(viewToWorld, worldToObj); if (m_frustumPick) { //The pick frustum is in view space. Transform to world space then object space. Frustum frustumObj = new Frustum(m_viewFrust0); frustumObj.Transform(viewToObj); //Multi-pick. Get everything in the pick frustum (m_viewFrust0). Vec3F eyeObj; worldToObj.Transform(camera.Eye, out eyeObj); userData.Clear(); if (geometricPick.IntersectFrustum(frustumObj, eyeObj, node.RenderState, userData)) { // Prepare a multi-pick HitRecord, as if OpenGL had calculated this. HitRecord hit = new HitRecord( node.GraphPath, renderObject, objToWorld, userData.ToArray()); m_geoHitList.Add(hit); } } else { //Single pick. We care about distance from camera eye. //Make a copy of the ray in world-space and tranform it to object space. Ray3F rayObj = rayWorld; //remember, Ray3F is a value type, not a reference type. rayObj.Transform(worldToObj); // Do the intersection test in object space. userData.Clear(); Vec3F intersectionPt, surfaceNormal; Vec3F nearestVert; bool intersected; intersected = geometricPick.IntersectRay( rayObj, camera, node.RenderState, objToWorld, this, out intersectionPt, out nearestVert, out surfaceNormal, userData); if (intersected) { // Transform to world space and then to screen space. objToWorld.Transform(intersectionPt, out intersectionPt); objToWorld.Transform(nearestVert, out nearestVert); // Prepare a single-pick HitRecord, as if OpenGL had calculated this. HitRecord hit = new HitRecord( node.GraphPath, renderObject, objToWorld, userData.ToArray()); // This is the one difference from OpenGL pick. We have the world pt already. hit.WorldIntersection = intersectionPt; hit.NearestVert = nearestVert; // Another difference is that it's possible to get the surface normal. if (surfaceNormal != Vec3F.ZeroVector) { objToWorld.TransformNormal(surfaceNormal, out surfaceNormal); surfaceNormal.Normalize(); hit.Normal = surfaceNormal; } m_geoHitList.Add(hit); } } } else { // Picking by "rendering", using OpenGL pick. PushMatrix(node.Transform, false); Gl.glPushName(index); IRenderPick pickInterface = renderObject as IRenderPick; if (pickInterface != null) { pickInterface.PickDispatch(node.GraphPath, node.RenderState, this, camera); } else { renderObject.Dispatch(node.GraphPath, node.RenderState, this, camera); } Gl.glPopName(); PopMatrix(); } } index++; } }
/// <summary> /// Gets the HitRecord array from a given traverse list</summary> /// <param name="traverseList">The traverse list</param> /// <param name="multiPick">True to return all available HitRecords. False to return just the closest</param> /// <returns>HitRecord array</returns> /// <remarks>Must be called after Init() and Dispatch(). There can potentially be duplicate /// HitRecords returned for two reasons: 1) Proxy objects, like the cube by default, render /// themselves twice, once with solid fill and once with the outline. Both renderings can /// yield a HitRecord, sometimes with slightly different z values. 2) Models are made up of /// multiple TransformNodes and each one can generate a HitRecord.</remarks> public HitRecord[] GetHits(ICollection <TraverseNode> traverseList, bool multiPick) { if (m_frustumPick == true && multiPick == false) { throw new InvalidOperationException("results can't be sorted front-to-back with frustum picking"); } // First get the hits from OpenGL pick. Gl.glRenderMode resets this value when it is called, // so consecutive calls to GetHits() will fail. Thus, we need to cache glRenderMode(GL_RENDER). if (m_openGlHits == 0) { m_openGlHits = Gl.glRenderMode(Gl.GL_RENDER); } HitRecord[] renderSelect = null; if (m_openGlHits > 0 && traverseList.Count > 0) { renderSelect = PopulateOpenGlSelection(traverseList); } else { renderSelect = s_emptyHitRecordArray; } // now integrate the hits from geometrical picking. HitRecord[] select; if (m_geoHitList.Count == 0) { select = renderSelect; } else { int total = renderSelect.Length + m_geoHitList.Count; select = new HitRecord[total]; renderSelect.CopyTo(select, 0); m_geoHitList.CopyTo(select, renderSelect.Length); } // sort the results by distance from camera eye, near-to-far if (m_frustumPick == false) { HitRecord.Sort(select, m_eye); } // if the caller only wants one result, just give them one. if (multiPick == false && select.Length > 1) { HitRecord[] singleHit = new HitRecord[1]; singleHit[0] = select[0]; select = singleHit; } // useful debug output //Console.WriteLine("multiPick:{0}, m_frustumPick:{1}", multiPick, m_frustumPick); //foreach (HitRecord hit in select) //{ // Console.WriteLine(" hit: {0} at z {1}", hit.RenderObject.InternalObject.Id, hit.ZMin); //} // return the results return(select); }