Skip to content

Commit

Permalink
DYN-6489 Dynamo Zoom Extents behavior has changed (#14674)
Browse files Browse the repository at this point in the history
* Adopt updated helix ZoomExtents algorithm

* use Helix native bounds implementation

* Why was this ever this way

* changes made

* handle zoom for ortho

* review comments

---------

Co-authored-by: Craig Long <[email protected]>
Co-authored-by: mjk.kirschner <[email protected]>
  • Loading branch information
3 people authored Dec 13, 2023
1 parent bcdeccb commit 6d50740
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 17 deletions.
20 changes: 9 additions & 11 deletions src/DynamoCoreWpf/ViewModels/Watch3D/HelixWatch3DViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3006,27 +3006,25 @@ internal static Rect3D ToRect3D(this BoundingBox bounds, double minRectSize = 0.
/// <returns>A <see cref="BoundingBox"/> object encapsulating the geometry.</returns>
internal static BoundingBox Bounds(this GeometryModel3D geom, float defaultBoundsSize = 5.0f)
{
if (geom.Geometry.Positions == null || geom.Geometry.Positions.Count == 0)
{
return new BoundingBox();
}
var bounds = geom.Bounds;

if (geom.Geometry.Positions.Count > 1)
//if the actual bounds diagonal are smaller than the default bounds diagonal then return
//a new default bounds centered on the actual bounds center.
if(bounds.Size.LengthSquared() < defaultBoundsSize * defaultBoundsSize * 3)
{
return BoundingBox.FromPoints(geom.Geometry.Positions.ToArray());
var pos = bounds.Center();
var min = pos + new Vector3(-defaultBoundsSize, -defaultBoundsSize, -defaultBoundsSize);
var max = pos + new Vector3(defaultBoundsSize, defaultBoundsSize, defaultBoundsSize);
return new BoundingBox(min, max);
}

var pos = geom.Geometry.Positions.First();
var min = pos + new Vector3(-defaultBoundsSize, -defaultBoundsSize, -defaultBoundsSize);
var max = pos + new Vector3(defaultBoundsSize, defaultBoundsSize, defaultBoundsSize);
return new BoundingBox(min, max);
return bounds;
}

public static Vector3 Center(this BoundingBox bounds)
{
return (bounds.Maximum + bounds.Minimum)/2;
}

}

internal static class Vector3Extensions
Expand Down
1 change: 0 additions & 1 deletion src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,6 @@ private void DynamoView_Loaded(object sender, EventArgs e)
BackgroundPreview = new Watch3DView { Name = BackgroundPreviewName };
background_grid.Children.Add(BackgroundPreview);
BackgroundPreview.DataContext = dynamoViewModel.BackgroundPreviewViewModel;
BackgroundPreview.Margin = new System.Windows.Thickness(0, 20, 0, 0);
var vizBinding = new Binding
{
Source = dynamoViewModel.BackgroundPreviewViewModel,
Expand Down
160 changes: 155 additions & 5 deletions src/DynamoCoreWpf/Views/Preview/Watch3DView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
using HelixToolkit.Wpf.SharpDX;
using HelixToolkit.SharpDX.Core;
using SharpDX;
using OrthographicCamera = HelixToolkit.Wpf.SharpDX.OrthographicCamera;
using Point = System.Windows.Point;
using HelixToolkit.SharpDX.Core.Cameras;

namespace Dynamo.Controls
{
Expand Down Expand Up @@ -172,18 +174,166 @@ private void ViewLoadedHandler(object sender, RoutedEventArgs e)

private void ViewModel_RequestZoomToFit(BoundingBox bounds)
{
var prevcamDir = watch_view.Camera.LookDirection;
watch_view.ZoomExtents(bounds.ToRect3D(.05));
var preCamDir = watch_view.Camera.LookDirection;
//TODO, Call the equivalent method in Helix on adoption of next release, remove these private helix definitions.

if (watch_view.Camera is HelixToolkit.Wpf.SharpDX.PerspectiveCamera perspectiveCam)
{
ZoomExtents(perspectiveCam, (float)(watch_view.ActualWidth / watch_view.ActualHeight), bounds, out var pos,
out var look, out var up);
perspectiveCam.AnimateTo(pos.ToPoint3D(), look.ToVector3D(), up.ToVector3D(), 0);
}

else if (watch_view.Camera is OrthographicCamera orthCam && watch_view.Camera.CameraInternal is OrthographicCameraCore orthoCamCore)
{
ZoomExtents(orthoCamCore, (float)(watch_view.ActualWidth / watch_view.ActualHeight), bounds, out var pos,
out var look, out var up,out var width
);
orthCam.AnimateWidth(width,0);
orthCam.AnimateTo(pos.ToPoint3D(), look.ToVector3D(), up.ToVector3D(), 0);
}

//if after a zoom the camera is in an undefined position or view direction, reset it.
if(watch_view.Camera.Position.ToVector3().IsUndefined() ||
watch_view.Camera.LookDirection.ToVector3().IsUndefined() ||
if (watch_view.Camera.Position.ToVector3().IsUndefined() ||
watch_view.Camera.LookDirection.ToVector3().IsUndefined() ||
watch_view.Camera.LookDirection.Length == 0)
{
watch_view.Camera.Position = prevCamera;
watch_view.Camera.LookDirection = prevcamDir;
watch_view.Camera.LookDirection = preCamDir;
}
}

#region ZoomExtents

//This implementation is found in the current dev branch of Helix-Toolkit (https://github.com/helix-toolkit/helix-toolkit/tree/develop) with the assumption it will be included in the 2.25.0 release
//The PR including these changes "Re-implementing zoom extents in sharpdx versions" is found here -> https://github.com/helix-toolkit/helix-toolkit/pull/2003
//Specifically the code included here is located in https://github.com/holance/helix-toolkit/blob/develop/Source/HelixToolkit.SharpDX.Shared/Extensions/CameraCoreExtensions.cs in this commit:
//https://github.com/holance/helix-toolkit/commit/660c85ff2218eb318f810dd682986d1816c5ece5
//This implementation is adjusted to remove the instance method pattern.
//This section can be removed when we adopt the next release of helix-toolkit and call the ZoomExtents() directly.

private static void ZoomExtents(HelixToolkit.Wpf.SharpDX.PerspectiveCamera camera, float aspectRatio, BoundingBox boundingBox, out Vector3 position, out Vector3 lookDir, out Vector3 upDir)
{
var cameraDir = Vector3.Normalize(camera.LookDirection.ToVector3());
var cameraUp = Vector3.Normalize(camera.UpDirection.ToVector3());
var cameraRight = Vector3.Cross(cameraDir, cameraUp);
cameraUp = Vector3.Cross(cameraRight, cameraDir);

var corners = boundingBox.GetCorners();

var frustum = new BoundingFrustum(camera.CreateViewMatrix() * camera.CreateProjectionMatrix(aspectRatio));
var leftNormal = -frustum.Left.Normal;
var rightNormal = -frustum.Right.Normal;
var topNormal = -frustum.Top.Normal;
var bottomNormal = -frustum.Bottom.Normal;

int leftMostPoint = -1, rightMostPoint = -1, topMostPoint = -1, bottomMostPoint = -1;
for (int i = 0; i < corners.Length; i++)
{
if (leftMostPoint < 0 && IsOutermostPointInDirection(i, ref leftNormal, corners))
{
leftMostPoint = i;
}
if (rightMostPoint < 0 && IsOutermostPointInDirection(i, ref rightNormal, corners))
{
rightMostPoint = i;
}
if (topMostPoint < 0 && IsOutermostPointInDirection(i, ref topNormal, corners))
{
topMostPoint = i;
}
if (bottomMostPoint < 0 && IsOutermostPointInDirection(i, ref bottomNormal, corners))
{
bottomMostPoint = i;
}
}

var plane1 = new Plane(corners[leftMostPoint], leftNormal);
var plane2 = new Plane(corners[rightMostPoint], rightNormal);
PlaneIntersectsPlane(ref plane1, ref plane2, out var horizontalIntersection);
plane1 = new Plane(corners[topMostPoint], topNormal);
plane2 = new Plane(corners[bottomMostPoint], bottomNormal);
PlaneIntersectsPlane(ref plane1, ref plane2, out var verticalIntersection);
FindClosestPointsOnTwoLines(ref horizontalIntersection, ref verticalIntersection, out var closestPointLine1, out var closestPointLine2);
position = Vector3.Dot(closestPointLine1 - closestPointLine2, cameraDir) < 0 ? closestPointLine1 : closestPointLine2;
upDir = cameraUp;
var boundPlane = new Plane(boundingBox.Center, cameraDir);
var lookRay = new Ray(position, cameraDir);
boundPlane.Intersects(ref lookRay, out float dist);
lookDir = cameraDir * dist;
}

private static void ZoomExtents(OrthographicCameraCore camera, float aspectRatio, BoundingBox boundingBox, out Vector3 position, out Vector3 lookDir, out Vector3 upDir, out float width)
{
float minX = float.PositiveInfinity, minY = float.PositiveInfinity, maxX = float.NegativeInfinity, maxY = float.NegativeInfinity;
var corners = boundingBox.GetCorners();
var view = camera.CreateViewMatrix();
foreach (var p in corners)
{
var local = Vector3.TransformCoordinate(p, view);
minX = Math.Min(minX, local.X);
minY = Math.Min(minY, local.Y);
maxX = Math.Max(maxX, local.X);
maxY = Math.Max(maxY, local.Y);
}
width = aspectRatio > 1 ? Math.Max((maxX - minX), (maxY - minY) * aspectRatio) : Math.Max((maxX - minX) / aspectRatio, maxY - minY);
position = boundingBox.Center - camera.LookDirection.Normalized() * width;
lookDir = camera.LookDirection.Normalized() * width;
upDir = camera.UpDirection;
}

private static bool IsOutermostPointInDirection(int pointIndex, ref Vector3 direction, Vector3[] corners)
{
Vector3 point = corners[pointIndex];
for (int i = 0; i < corners.Length; i++)
{
if (i != pointIndex && Vector3.Dot(direction, corners[i] - point) > 0)
return false;
}

return true;
}

// Credit: http://wiki.unity3d.com/index.php/3d_Math_functions
// Returns the edge points of the closest line segment between 2 lines
private static void FindClosestPointsOnTwoLines(ref Ray line1, ref Ray line2, out Vector3 closestPointLine1, out Vector3 closestPointLine2)
{
Vector3 line1Direction = line1.Direction;
Vector3 line2Direction = line2.Direction;

float a = Vector3.Dot(line1Direction, line1Direction);
float b = Vector3.Dot(line1Direction, line2Direction);
float e = Vector3.Dot(line2Direction, line2Direction);

float d = a * e - b * b;

Vector3 r = line1.Position - line2.Position;
float c = Vector3.Dot(line1Direction, r);
float f = Vector3.Dot(line2Direction, r);

float s = (b * f - c * e) / d;
float t = (a * f - c * b) / d;

closestPointLine1 = line1.Position + line1Direction * s;
closestPointLine2 = line2.Position + line2Direction * t;
}

private static bool PlaneIntersectsPlane(ref Plane p1, ref Plane p2, out Ray intersection)
{
var dir = Vector3.Cross(p1.Normal, p2.Normal);
float det = Vector3.Dot(dir, dir);
if (Math.Abs(det) > float.Epsilon)
{
var p = (Vector3.Cross(dir, p2.Normal) * p1.D + Vector3.Cross(p1.Normal, dir) * p2.D) / det;
intersection = new Ray(p, dir);
return true;
}
intersection = default;
return false;
}

#endregion

private void RequestViewRefreshHandler()
{
View.InvalidateRender();
Expand Down

0 comments on commit 6d50740

Please sign in to comment.