@@ -93,6 +93,30 @@ public class OpenGLSetupPreviewDrawingEngine : OpenGLDrawingEngineBase<ILightPro
9393 /// </summary>
9494 private float _rubberBandStartY ;
9595
96+ /// <summary>
97+ /// Top left X coordinate in screen coordinates of the normalized rubberband selection rectangle.
98+ /// Normalized such that the ruberband is always oriented left to right, top to bottom.
99+ /// </summary>
100+ private float _normalizedRubberbandPt1X ;
101+
102+ /// <summary>
103+ /// Top left Y coordinate in screen coordinates of the normalized rubberband selection rectangle.
104+ /// Normalized such that the ruberband is always oriented left to right, top to bottom.
105+ /// </summary>
106+ private float _normalizedRubberbandPt1Y ;
107+
108+ /// <summary>
109+ /// Bottom right X coordinate in screen coordinates of the normalized rubberband selection rectangle.
110+ /// Normalized such that the ruberband is always oriented left to right, top to bottom.
111+ /// </summary>
112+ private float _normalizedRubberbandPt2X ;
113+
114+ /// <summary>
115+ /// Bottom right Y coordinate in screen coordinates of the normalized rubberband selection rectangle.
116+ /// Normalized such that the ruberband is always oriented left to right, top to bottom.
117+ /// </summary>
118+ private float _normalizedRubberbandPt2Y ;
119+
96120 #endregion
97121
98122 #region Private Properties
@@ -445,59 +469,19 @@ public bool MouseMove(int previousMouseX, int previousMouseY, int mouseX, int mo
445469 _rubberbandPrimitive . Vertices . Add ( _rubberbandStartInWorld . X ) ;
446470 _rubberbandPrimitive . Vertices . Add ( endPoint . Y ) ;
447471 _rubberbandPrimitive . Vertices . Add ( 0.0f ) ;
448-
449- // Declare some screen coordinates
450- float pt1X ;
451- float pt1Y ;
452- float pt2X ;
453- float pt2Y ;
454-
472+
455473 // To simplify the logic always assuming the rubberband is
456- // moving from left to right, top to bottom.
457- // The following logic keeps the points in this order.
458-
459- // If the rubberband start X position is less than the current mouse X position then...
460- if ( _rubberBandStartX < mouseX )
461- {
462- // If the rubberband start Y position is less than the current mouse Y position then...
463- if ( _rubberBandStartY < mouseY )
464- {
465- pt1X = _rubberBandStartX ;
466- pt1Y = _rubberBandStartY ;
467- pt2X = mouseX ;
468- pt2Y = mouseY ;
469- }
470- else
471- {
472- pt1X = _rubberBandStartX ;
473- pt1Y = mouseY ;
474- pt2X = mouseX ;
475- pt2Y = _rubberBandStartY ;
476- }
477- }
478- else
479- {
480- if ( _rubberBandStartY < mouseY )
481- {
482- pt1X = mouseX ;
483- pt1Y = _rubberBandStartY ;
484- pt2X = _rubberBandStartX ;
485- pt2Y = mouseY ;
486- }
487- else
488- {
489- pt1X = mouseX ;
490- pt1Y = mouseY ;
491- pt2X = _rubberBandStartX ;
492- pt2Y = _rubberBandStartY ;
493- }
494- }
495-
474+ // moving from left to right, top to bottom.
475+ _normalizedRubberbandPt1X = Math . Min ( _rubberBandStartX , mouseX ) ;
476+ _normalizedRubberbandPt2X = Math . Max ( _rubberBandStartX , mouseX ) ;
477+ _normalizedRubberbandPt1Y = Math . Min ( _rubberBandStartY , mouseY ) ;
478+ _normalizedRubberbandPt2Y = Math . Max ( _rubberBandStartY , mouseY ) ;
479+
496480 // Convert the screen coordinates into 3-D world coordinates
497- Vector3 leftTop = GetMousePointInWorld ( new Vector2 ( pt1X , pt1Y ) ) ;
498- Vector3 rightTop = GetMousePointInWorld ( new Vector2 ( pt2X , pt1Y ) ) ;
499- Vector3 bottomRight = GetMousePointInWorld ( new Vector2 ( pt2X , pt2Y ) ) ;
500- Vector3 bottomLeft = GetMousePointInWorld ( new Vector2 ( pt1X , pt2Y ) ) ;
481+ Vector3 leftTop = GetMousePointInWorld ( new Vector2 ( _normalizedRubberbandPt1X , _normalizedRubberbandPt1Y ) ) ;
482+ Vector3 rightTop = GetMousePointInWorld ( new Vector2 ( _normalizedRubberbandPt2X , _normalizedRubberbandPt1Y ) ) ;
483+ Vector3 bottomRight = GetMousePointInWorld ( new Vector2 ( _normalizedRubberbandPt2X , _normalizedRubberbandPt2Y ) ) ;
484+ Vector3 bottomLeft = GetMousePointInWorld ( new Vector2 ( _normalizedRubberbandPt1X , _normalizedRubberbandPt2Y ) ) ;
501485
502486 // Loop over all the props
503487 foreach ( IPropOpenGLData prop in Props )
@@ -528,7 +512,7 @@ public bool MouseMove(int previousMouseX, int previousMouseY, int mouseX, int mo
528512 {
529513 // If the prop intersects the rubberband selection or
530514 // contains the entire prop then...
531- if ( IntersectsProp ( leftTop , rightTop , bottomRight , bottomLeft , prop . GetMinimum ( ) , prop . GetMaximum ( ) ) ||
515+ if ( IntersectsProp ( prop . GetMinimum ( ) , prop . GetMaximum ( ) ) ||
532516 ContainsProp ( leftTop , bottomRight , prop . GetMinimum ( ) , prop . GetMaximum ( ) ) )
533517 {
534518 // If the prop is not already selected then...
@@ -636,25 +620,18 @@ public bool MouseMove(int previousMouseX, int previousMouseY, int mouseX, int mo
636620
637621 /// <summary>
638622 /// Returns true if the prop represented by the minimum and maximum vectors intersect with
639- /// the rectangle formed by topLeft, topRight, bottomRight, and bottomLeft.
640- /// </summary>
641- /// <param name="topLeft">Top left point of the rectangle</param>
642- /// <param name="bottomRight">Bottom right point of the rectangle</param>
623+ /// the rubberband frustum.
624+ /// </summary>
643625 /// <param name="minimum">Minimum values of the prop along each axis</param>
644626 /// <param name="maximum">Maximum values of the prop along each axis</param>
645- /// <returns>True if the prop intersects with the rectangle</returns>
646- private bool IntersectsProp ( Vector3 topLeft , Vector3 topRight , Vector3 bottomRight , Vector3 bottomLeft , Vector3 minimum , Vector3 maximum )
627+ /// <returns>True if the prop intersects with the frustum created from the rubberband rectangle</returns>
628+ private bool IntersectsProp (
629+ Vector3 minimum ,
630+ Vector3 maximum )
647631 {
648- // Return whether the prop intersects with the rectangle
649- return
650- ( ( topLeft . X >= minimum . X && topLeft . X <= maximum . X &&
651- topLeft . Y >= minimum . Y && topLeft . Y <= maximum . Y ) ||
652- ( bottomRight . X >= minimum . X && bottomRight . X <= maximum . X &&
653- bottomRight . Y >= minimum . Y && bottomRight . Y <= maximum . Y ) ||
654- ( topRight . X >= minimum . X && topRight . X <= maximum . X &&
655- topRight . Y >= minimum . Y && topRight . Y <= maximum . Y ) ||
656- ( bottomLeft . X >= minimum . X && bottomLeft . X <= maximum . X &&
657- bottomLeft . Y >= minimum . Y && bottomLeft . Y <= maximum . Y ) ) ;
632+ // Check to see if the prop intersects with the rubberband frustum
633+ return IsBoxIntersectingFrustum (
634+ CreateSelectionFrustum ( ) , minimum , maximum ) ;
658635 }
659636
660637 /// <summary>
@@ -1277,8 +1254,8 @@ private Vector3 GetMouseMovementInWorld(int previousMouseX, int previousMouseY,
12771254
12781255 float depth = ( clip . Z + 1.0f ) * 0.5f ; // convert NDC Z → depth buffer range
12791256
1280- var lastWorld = Unproject ( new Vector3 ( previousMouseX , previousMouseY , depth ) , Camera . ViewMatrix , CreatePerspective ( ) , new Size ( OpenTkControl_Width , OpenTkControl_Height ) ) ;
1281- var currWorld = Unproject ( new Vector3 ( mouseX , mouseY , depth ) , Camera . ViewMatrix , CreatePerspective ( ) , new Size ( OpenTkControl_Width , OpenTkControl_Height ) ) ;
1257+ Vector3 lastWorld = Unproject ( previousMouseX , previousMouseY , depth , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1258+ Vector3 currWorld = Unproject ( mouseX , mouseY , depth , Camera . ViewMatrix , CreatePerspective ( ) ) ;
12821259
12831260 propPos = currWorld - lastWorld ;
12841261
@@ -1288,34 +1265,105 @@ private Vector3 GetMouseMovementInWorld(int previousMouseX, int previousMouseY,
12881265 /// <summary>
12891266 /// Unprojects a screen coordinate into world coordinates.
12901267 /// </summary>
1291- /// <param name="screenPos">Screen position to unproject</param>
1268+ /// <param name="screenX">Screen position on the X axis</param>
1269+ /// <param name="screenY">Screen position on the Y axis</param>
1270+ /// <param name="depth">
1271+ /// The depth value in the range [0.0, 1.0], where 0.0 represents
1272+ /// the Near clipping plane and 1.0 represents the Far clipping plane.
1273+ /// </param>
12921274 /// <param name="view">View matrix</param>
12931275 /// <param name="proj">Project matrix</param>
1294- /// <param name="viewport">Size of the viewport</param>
12951276 /// <returns>Screen position in world coordinates</returns>
1296- private Vector3 Unproject ( Vector3 screenPos , Matrix4 view , Matrix4 proj , Size viewport )
1277+ private Vector3 Unproject ( float screenX , float screenY , float depth , Matrix4 view , Matrix4 proj )
12971278 {
1298- Vector4 vec ;
1279+ // 1. Map Screen Pixels to NDC (-1 to 1)
1280+ float ndcX = ( 2.0f * screenX / OpenTkControl_Width ) - 1.0f ;
1281+ float ndcY = 1.0f - ( 2.0f * screenY / OpenTkControl_Height ) ;
12991282
1300- vec . X = 2.0f * screenPos . X / viewport . Width - 1.0f ;
1301- vec . Y = 1.0f - 2.0f * screenPos . Y / viewport . Height ;
1302- vec . Z = screenPos . Z * 2.0f - 1.0f ;
1303- vec . W = 1.0f ;
1283+ // 2. Map Depth (0 to 1) to NDC (-1 to 1)
1284+ // This is the missing piece in your second method!
1285+ float ndcZ = depth ; //(depth * 2.0f) - 1.0f;
13041286
1305- Matrix4 inv = Matrix4 . Invert ( view * proj ) ;
1306- // Vector4 result = OpenTK.Mathematics.Vector4.Transform(vec, inv);
1307- Vector4 result = new Vector4 (
1308- vec . X * inv . M11 + vec . Y * inv . M21 + vec . Z * inv . M31 + vec . W * inv . M41 ,
1309- vec . X * inv . M12 + vec . Y * inv . M22 + vec . Z * inv . M32 + vec . W * inv . M42 ,
1310- vec . X * inv . M13 + vec . Y * inv . M23 + vec . Z * inv . M33 + vec . W * inv . M43 ,
1311- vec . X * inv . M14 + vec . Y * inv . M24 + vec . Z * inv . M34 + vec . W * inv . M44 ) ;
1287+ // 3. Transform by Inverse View-Projection
1288+ Matrix4 invVP = Matrix4 . Invert ( view * proj ) ;
1289+ Vector4 ndc = new Vector4 ( ndcX , ndcY , ndcZ , 1.0f ) ;
1290+
1291+ // Using TransformRow to match OpenTK's Matrix * Vector convention
1292+ Vector4 world = Vector4 . TransformRow ( ndc , invVP ) ;
1293+
1294+ // 4. Perspective Divide (Crucial for 3D depth)
1295+ if ( Math . Abs ( world . W ) > float . Epsilon )
1296+ {
1297+ return world . Xyz / world . W ;
1298+ }
1299+
1300+ return world . Xyz ;
1301+ }
13121302
1313- if ( result . W > float . Epsilon )
1314- result /= result . W ;
1303+ /// <summary>
1304+ /// Creates the rubberband selection frustum.
1305+ /// </summary>
1306+ /// <returns>Selection frustum as a collection of planes</returns>
1307+ private List < Plane > CreateSelectionFrustum ( )
1308+ {
1309+ // Top-Left corner on the Near plane
1310+ Vector3 ptTL_Near = Unproject ( _normalizedRubberbandPt1X , _normalizedRubberbandPt1Y , 0.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1311+ // Bottom-Right corner on the Near plane
1312+ Vector3 ptBR_Near = Unproject ( _normalizedRubberbandPt2X , _normalizedRubberbandPt2Y , 0.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1313+ Vector3 ptTR_Near = Unproject ( _normalizedRubberbandPt2X , _normalizedRubberbandPt1Y , 0.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1314+ Vector3 ptBL_Near = Unproject ( _normalizedRubberbandPt1X , _normalizedRubberbandPt2Y , 0.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1315+
1316+ // Top-Left corner on the Far plane
1317+ Vector3 ptTL_Far = Unproject ( _normalizedRubberbandPt1X , _normalizedRubberbandPt1Y , 1.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1318+ // Bottom-Right corner on the Far plane
1319+ Vector3 ptBR_Far = Unproject ( _normalizedRubberbandPt2X , _normalizedRubberbandPt2Y , 1.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1320+ Vector3 ptTR_Far = Unproject ( _normalizedRubberbandPt2X , _normalizedRubberbandPt1Y , 1.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1321+ Vector3 ptBL_Far = Unproject ( _normalizedRubberbandPt1X , _normalizedRubberbandPt2Y , 1.0f , Camera . ViewMatrix , CreatePerspective ( ) ) ;
1322+
1323+ List < Plane > selectionFrustum = new List < Plane >
1324+ {
1325+ new Plane ( ptTL_Near , ptBL_Near , ptTR_Near ) , // Near Plane
1326+ new Plane ( ptTL_Far , ptTR_Far , ptBL_Far ) , // Far Plane
1327+ new Plane ( ptBL_Near , ptTL_Near , ptBL_Far ) , // Left Plane
1328+ new Plane ( ptTR_Near , ptBR_Near , ptTR_Far ) , // Right Plane
1329+ new Plane ( ptTL_Near , ptTR_Near , ptTL_Far ) , // Top Plane
1330+ new Plane ( ptBL_Near , ptBL_Far , ptBR_Near ) // Bottom Plane
1331+ } ;
1332+
1333+ return selectionFrustum ;
1334+ }
13151335
1316- return new Vector3 ( result . X , result . Y , result . Z ) ;
1336+ /// <summary>
1337+ /// Returns true if the specified box intersects the frustum.
1338+ /// </summary>
1339+ /// <param name="frustum">Frustum to test</param>
1340+ /// <param name="boxMin">Box minimum point</param>
1341+ /// <param name="boxMax">Box maximum point</param>
1342+ /// <returns>True if the box intersects the frustum</returns>
1343+ private bool IsBoxIntersectingFrustum ( List < Plane > frustum , Vector3 boxMin , Vector3 boxMax )
1344+ {
1345+ // Loop over the planes in the frustum
1346+ foreach ( Plane plane in frustum )
1347+ {
1348+ // The "Negative Vertex" is the corner of the box furthest OPPOSITE the normal
1349+ Vector3 negativeVertex = new Vector3 (
1350+ plane . Normal . X >= 0 ? boxMin . X : boxMax . X ,
1351+ plane . Normal . Y >= 0 ? boxMin . Y : boxMax . Y ,
1352+ plane . Normal . Z >= 0 ? boxMin . Z : boxMax . Z
1353+ ) ;
1354+
1355+ // If the point furthest along the normal is behind the plane,
1356+ // the entire box is definitely outside this plane.
1357+ if ( Vector3 . Dot ( plane . Normal , negativeVertex ) > plane . Distance )
1358+ {
1359+ // Note: If you find ONE plane where the box is entirely on the "outside"
1360+ // side, there is no intersection.
1361+ return false ;
1362+ }
1363+ }
1364+ return true ;
13171365 }
13181366
13191367 #endregion
1320- }
1368+ }
13211369}
0 commit comments