Orthographic projection mode with dynamic near/far.
This commit is contained in:
parent
0834aff9f4
commit
8fafc54f90
70 changed files with 2548 additions and 244 deletions
|
|
@ -34,6 +34,7 @@ using MatterHackers.Agg;
|
|||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
using MatterHackers.RenderOpenGl.OpenGl;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.GCodeVisualizer
|
||||
{
|
||||
|
|
@ -330,12 +331,11 @@ namespace MatterHackers.GCodeVisualizer
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Render3D(GCodeRenderInfo renderInfo, DrawEventArgs e)
|
||||
bool PrepareForGeometryGeneration(GCodeRenderInfo renderInfo)
|
||||
{
|
||||
if (renderInfo == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (layerVertexBuffer == null)
|
||||
|
|
@ -359,7 +359,12 @@ namespace MatterHackers.GCodeVisualizer
|
|||
lastRenderType = renderInfo.CurrentRenderType;
|
||||
}
|
||||
|
||||
if (all.layers.Count > 0)
|
||||
return all.layers.Count > 0;
|
||||
}
|
||||
|
||||
public void Render3D(GCodeRenderInfo renderInfo, DrawEventArgs e)
|
||||
{
|
||||
if (PrepareForGeometryGeneration(renderInfo))
|
||||
{
|
||||
for (int i = renderInfo.EndLayerIndex - 1; i >= renderInfo.StartLayerIndex; i--)
|
||||
{
|
||||
|
|
@ -425,5 +430,28 @@ namespace MatterHackers.GCodeVisualizer
|
|||
GL.PopAttrib();
|
||||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetAabbOfRender3D(GCodeRenderInfo renderInfo)
|
||||
{
|
||||
var box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
if (PrepareForGeometryGeneration(renderInfo))
|
||||
{
|
||||
for (int i = renderInfo.EndLayerIndex - 1; i >= renderInfo.StartLayerIndex; i--)
|
||||
{
|
||||
if (i < layerVertexBuffer.Count)
|
||||
{
|
||||
if (layerVertexBuffer[i] == null)
|
||||
{
|
||||
layerVertexBuffer[i] = Create3DDataForLayer(i, renderInfo);
|
||||
}
|
||||
|
||||
box = AxisAlignedBoundingBox.Union(box, layerVertexBuffer[i].BoundingBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ using MatterHackers.Agg;
|
|||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
using MatterHackers.RenderOpenGl.OpenGl;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.GCodeVisualizer
|
||||
{
|
||||
|
|
@ -48,6 +49,10 @@ namespace MatterHackers.GCodeVisualizer
|
|||
|
||||
private ColorVertexData[] colorVertexData;
|
||||
|
||||
private AxisAlignedBoundingBox boundingBox = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
public AxisAlignedBoundingBox BoundingBox { get { return new AxisAlignedBoundingBox(boundingBox.MinXYZ, boundingBox.MaxXYZ); } }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new VertexBuffer
|
||||
/// </summary>
|
||||
|
|
@ -136,6 +141,13 @@ namespace MatterHackers.GCodeVisualizer
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
boundingBox = AxisAlignedBoundingBox.Empty();
|
||||
foreach (int i in indexData)
|
||||
{
|
||||
var v = colorData[i];
|
||||
boundingBox.ExpandToInclude(new Vector3Float(v.positionX, v.positionY, v.positionZ));
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderRange(int offset, int count)
|
||||
|
|
|
|||
|
|
@ -542,6 +542,23 @@ namespace MatterHackers.MatterControl
|
|||
}
|
||||
}
|
||||
|
||||
internal AxisAlignedBoundingBox GetAabbOfRenderGCode3D()
|
||||
{
|
||||
if (this.RenderInfo != null)
|
||||
{
|
||||
// If needed, update the RenderType flags to match to current user selection
|
||||
if (RendererOptions.IsDirty)
|
||||
{
|
||||
this.RenderInfo.RefreshRenderType();
|
||||
RendererOptions.IsDirty = false;
|
||||
}
|
||||
|
||||
return this.GCodeRenderer.GetAabbOfRender3D(this.RenderInfo);
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public void LoadActiveSceneGCode(string filePath, CancellationToken cancellationToken, Action<double, string> progressReporter)
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
|
|
|
|||
|
|
@ -163,6 +163,15 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
}
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IObject3DControl.GetWorldspaceAABB()
|
||||
{
|
||||
// TODO: Untested.
|
||||
if (flattened != null)
|
||||
return new AxisAlignedBoundingBox(targets.Select(t => t.Point).ToArray());
|
||||
else
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// Clear and close selection targets
|
||||
|
|
|
|||
|
|
@ -201,6 +201,27 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
IObject3D selectedItem = RootSelection;
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (Object3DControlContext.SelectedObject3DControl == null)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, rotationHandle.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
|
||||
if (mouseMoveInfo != null || mouseDownInfo != null || MouseIsOver)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, GetRotationCompassAABB(selectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public Vector3 GetCornerPosition(IObject3D objectBeingRotated)
|
||||
{
|
||||
return GetCornerPosition(objectBeingRotated, out _);
|
||||
|
|
@ -326,7 +347,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
}
|
||||
|
||||
var hitPlane = new PlaneShape(RotationPlanNormal, RotationPlanNormal.Dot(controlCenter), null);
|
||||
IntersectInfo hitOnRotationPlane = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
IntersectInfo hitOnRotationPlane = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
if (hitOnRotationPlane != null)
|
||||
{
|
||||
AxisAlignedBoundingBox currentSelectedBounds = selectedItem.GetAxisAlignedBoundingBox();
|
||||
|
|
@ -645,6 +666,42 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
}
|
||||
}
|
||||
|
||||
private AxisAlignedBoundingBox GetRotationCompassAABB(IObject3D selectedItem)
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
if (Object3DControlContext.Scene.SelectedItem == null)
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox currentSelectedBounds = selectedItem.GetAxisAlignedBoundingBox();
|
||||
if (currentSelectedBounds.XSize > 100000)
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
if (mouseMoveInfo != null)
|
||||
{
|
||||
Matrix4X4 rotationCenterTransform = GetRotationTransform(selectedItem, out double radius);
|
||||
|
||||
double innerRadius = radius + RingWidth / 2;
|
||||
double outerRadius = innerRadius + RingWidth;
|
||||
double snappingMarkRadius = outerRadius + 20;
|
||||
|
||||
if (mouseDownInfo != null || MouseIsOver)
|
||||
{
|
||||
double snapMarkerRadius = GuiWidget.DeviceScale * 10;
|
||||
double snapMarkerOuterRadius = snappingMarkRadius + snapMarkerRadius;
|
||||
box = AxisAlignedBoundingBox.Union(box, new AxisAlignedBoundingBox(-snapMarkerOuterRadius, -snapMarkerOuterRadius, 0, snapMarkerOuterRadius, snapMarkerOuterRadius, 0).NewTransformed(rotationCenterTransform));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool ForceHideAngle()
|
||||
{
|
||||
return (Object3DControlContext.HoveredObject3DControl != this
|
||||
|
|
|
|||
|
|
@ -171,15 +171,30 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
Object3DControlContext.GuiSurface.BeforeDraw -= Object3DControl_BeforeDraw;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
private bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = controlVisible == null ? true : controlVisible();
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
&& Object3DControlContext.SelectedObject3DControl as ScaleDiameterControl == null)
|
||||
{
|
||||
shouldDrawScaleControls = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Matrix4X4 GetRingTransform()
|
||||
{
|
||||
Vector3 newBottomCenter = ObjectSpace.GetCenterPosition(RootSelection, placement);
|
||||
var rotation = Matrix4X4.CreateRotation(new Quaternion(RootSelection.Matrix));
|
||||
var translation = Matrix4X4.CreateTranslation(newBottomCenter);
|
||||
return rotation * translation;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
|
|
@ -201,10 +216,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
GLHelper.Render(grabControlMesh, color.WithAlpha(e.Alpha0to255), TotalTransform, RenderTypes.Shaded);
|
||||
}
|
||||
|
||||
Vector3 newBottomCenter = ObjectSpace.GetCenterPosition(selectedItem, placement);
|
||||
var rotation = Matrix4X4.CreateRotation(new Quaternion(selectedItem.Matrix));
|
||||
var translation = Matrix4X4.CreateTranslation(newBottomCenter);
|
||||
Object3DControlContext.World.RenderRing(rotation * translation, Vector3.Zero, getDiameters[diameterIndex](), 60, color.WithAlpha(e.Alpha0to255), 2, 0, e.ZBuffered);
|
||||
Object3DControlContext.World.RenderRing(GetRingTransform(), Vector3.Zero, getDiameters[diameterIndex](), 60, color.WithAlpha(e.Alpha0to255), 2, 0, e.ZBuffered);
|
||||
}
|
||||
|
||||
if (hitPlane != null)
|
||||
|
|
@ -223,6 +235,35 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, grabControlMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
|
||||
var xform = GetRingTransform();
|
||||
var radius = getDiameters[diameterIndex]() / 2;
|
||||
box = AxisAlignedBoundingBox.Union(box, new AxisAlignedBoundingBox(-radius, -radius, 0, radius, radius, 0).NewTransformed(xform));
|
||||
}
|
||||
|
||||
if (shouldDrawScaleControls && (MouseIsOver || MouseDownOnControl))
|
||||
{
|
||||
var (a, b, c, d) = GetMeasureLine();
|
||||
box = AxisAlignedBoundingBox.Union(box, new AxisAlignedBoundingBox(new Vector3[] { a, b, c, d }));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public override void OnMouseDown(Mouse3DEventArgs mouseEvent3D)
|
||||
{
|
||||
var selectedItem = RootSelection;
|
||||
|
|
@ -269,7 +310,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl && hitPlane != null)
|
||||
{
|
||||
var info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
var info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -194,10 +194,9 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
Object3DControlContext.GuiSurface.BeforeDraw -= Object3DControl_BeforeDraw;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = true;
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
&& Object3DControlContext.SelectedObject3DControl as ScaleHeightControl == null)
|
||||
|
|
@ -205,6 +204,14 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
shouldDrawScaleControls = false;
|
||||
}
|
||||
|
||||
return shouldDrawScaleControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
|
|
@ -251,6 +258,27 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, topScaleMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
|
||||
box.ExpandToInclude(GetTopPosition(selectedItem));
|
||||
box.ExpandToInclude(GetBottomPosition(selectedItem));
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public Vector3 GetBottomPosition(IObject3D selectedItem)
|
||||
{
|
||||
var meshBounds = selectedItem.GetAxisAlignedBoundingBox(selectedItem.Matrix.Inverted);
|
||||
|
|
@ -316,7 +344,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl)
|
||||
{
|
||||
IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
IntersectInfo info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
transformAppliedByThis = selectedItem.Matrix;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = true;
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
|
|
@ -162,6 +162,12 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
{
|
||||
shouldDrawScaleControls = false;
|
||||
}
|
||||
return shouldDrawScaleControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
|
|
@ -201,6 +207,40 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null && Object3DControlContext.Scene.ShowSelectionShadow)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, minXminYMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
|
||||
Vector3 startPosition = GetCornerPosition(selectedItem, quadrantIndex);
|
||||
Vector3 endPosition = GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4);
|
||||
box.ExpandToInclude(startPosition);
|
||||
box.ExpandToInclude(endPosition);
|
||||
|
||||
if (MouseIsOver || MouseDownOnControl)
|
||||
{
|
||||
var (a0, a1, a2, a3) = GetMeasureLine(selectedItem, quadrantIndex);
|
||||
var (b0, b1, b2, b3) = GetMeasureLine(selectedItem, quadrantIndex + 1);
|
||||
box = AxisAlignedBoundingBox.Union(box, new AxisAlignedBoundingBox(new Vector3[] {
|
||||
a0, a1, a2, a3,
|
||||
b0, b1, b2, b3,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
|
||||
private (Vector3 start0, Vector3 end0, Vector3 start1, Vector3 end1) GetMeasureLine(IObject3D selectedItem, int quadrant)
|
||||
{
|
||||
var corner = new Vector3[4];
|
||||
|
|
@ -308,7 +348,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl && hitPlane != null)
|
||||
{
|
||||
IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
IntersectInfo info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
transformAppliedByThis = selectedItem.Matrix;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = true;
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
|
|
@ -175,6 +175,12 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
{
|
||||
shouldDrawScaleControls = false;
|
||||
}
|
||||
return shouldDrawScaleControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
|
|
@ -200,6 +206,24 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, minXminYMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public Vector3 GetCornerPosition(IObject3D item, int quadrantIndex)
|
||||
{
|
||||
AxisAlignedBoundingBox originalSelectedBounds = item.GetAxisAlignedBoundingBox();
|
||||
|
|
@ -275,7 +299,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl && hitPlane != null)
|
||||
{
|
||||
IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
IntersectInfo info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -146,16 +146,22 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
Object3DControlContext.GuiSurface.BeforeDraw += Object3DControl_BeforeDraw;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = true;
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
&& Object3DControlContext.SelectedObject3DControl as ScaleMatrixTopControl == null)
|
||||
{
|
||||
shouldDrawScaleControls = false;
|
||||
}
|
||||
return shouldDrawScaleControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
|
|
@ -205,6 +211,31 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, topScaleMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
|
||||
Vector3 topPosition = GetTopPosition(selectedItem);
|
||||
var bottomPosition = topPosition;
|
||||
var originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox();
|
||||
bottomPosition.Z = originalSelectedBounds.MinXYZ.Z;
|
||||
box.ExpandToInclude(topPosition);
|
||||
box.ExpandToInclude(bottomPosition);
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public Vector3 GetTopPosition(IObject3D selectedItem)
|
||||
{
|
||||
AxisAlignedBoundingBox originalSelectedBounds = selectedItem.GetAxisAlignedBoundingBox();
|
||||
|
|
@ -250,7 +281,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl)
|
||||
{
|
||||
IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
IntersectInfo info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
Object3DControlContext.GuiSurface.BeforeDraw -= Object3DControl_BeforeDraw;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = true;
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
|
|
@ -178,6 +178,12 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
{
|
||||
shouldDrawScaleControls = false;
|
||||
}
|
||||
return shouldDrawScaleControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
|
|
@ -222,6 +228,39 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, minXminYMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
|
||||
Vector3 startPosition = ObjectSpace.GetCornerPosition(selectedItem, quadrantIndex);
|
||||
Vector3 endPosition = ObjectSpace.GetCornerPosition(selectedItem, (quadrantIndex + 1) % 4);
|
||||
box.ExpandToInclude(startPosition);
|
||||
box.ExpandToInclude(endPosition);
|
||||
|
||||
if (MouseIsOver || MouseDownOnControl)
|
||||
{
|
||||
var (a0, a1, a2, a3) = GetMeasureLine(quadrantIndex);
|
||||
var (b0, b1, b2, b3) = GetMeasureLine(quadrantIndex + 1);
|
||||
box = AxisAlignedBoundingBox.Union(box, new AxisAlignedBoundingBox(new Vector3[] {
|
||||
a0, a1, a2, a3,
|
||||
b0, b1, b2, b3
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public override void OnMouseDown(Mouse3DEventArgs mouseEvent3D)
|
||||
{
|
||||
var selectedItem = RootSelection;
|
||||
|
|
@ -272,7 +311,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl && hitPlane != null)
|
||||
{
|
||||
var info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
var info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
Object3DControlContext.GuiSurface.BeforeDraw -= Object3DControl_BeforeDraw;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawScaleControls()
|
||||
{
|
||||
bool shouldDrawScaleControls = true;
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
|
|
@ -203,6 +203,12 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
{
|
||||
shouldDrawScaleControls = false;
|
||||
}
|
||||
return shouldDrawScaleControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
|
|
@ -239,6 +245,32 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawScaleControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, minXminYMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
|
||||
if (MouseIsOver || MouseDownOnControl)
|
||||
{
|
||||
var (a0, a1, a2, a3) = GetMeasureLine(selectedItem);
|
||||
box = AxisAlignedBoundingBox.Union(box, new AxisAlignedBoundingBox(new Vector3[] {
|
||||
a0, a1, a2, a3
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public override void OnMouseDown(Mouse3DEventArgs mouseEvent3D)
|
||||
{
|
||||
var selectedItem = RootSelection;
|
||||
|
|
@ -289,7 +321,7 @@ namespace MatterHackers.Plugins.EditorTools
|
|||
|
||||
if (MouseDownOnControl && hitPlane != null)
|
||||
{
|
||||
var info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
var info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null)
|
||||
|
|
|
|||
|
|
@ -30,12 +30,16 @@ either expressed or implied, of the FreeBSD Project.
|
|||
using System.Collections.Generic;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public interface IEditorDraw
|
||||
{
|
||||
void DrawEditor(Object3DControlsLayer object3DControlLayer, DrawEventArgs e);
|
||||
|
||||
/// <returns>The worldspace AABB of any 3D editing geometry drawn by DrawEditor.</returns>
|
||||
AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer);
|
||||
}
|
||||
|
||||
public interface ICustomEditorDraw : IEditorDraw
|
||||
|
|
|
|||
|
|
@ -93,6 +93,19 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
&& layer.Scene.SelectedItem.DescendantsAndSelf().Where((i) => i == this).Any())
|
||||
{
|
||||
var currentMatrixInv = Matrix.Inverted;
|
||||
var aabb = this.GetAxisAlignedBoundingBox(currentMatrixInv);
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(Diameter, Diameter, aabb.ZSize), new Vector3(rotationCenter, aabb.Center.Z)).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public override void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|
|
|
|||
|
|
@ -253,24 +253,32 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
|
||||
switch (MaintainRatio)
|
||||
{
|
||||
case MaintainRatio.None:
|
||||
break;
|
||||
case MaintainRatio.X_Y:
|
||||
var minXy = Math.Min(scale.X, scale.Y);
|
||||
scale.X = minXy;
|
||||
scale.Y = minXy;
|
||||
break;
|
||||
case MaintainRatio.X_Y_Z:
|
||||
var minXyz = Math.Min(Math.Min(scale.X, scale.Y), scale.Z);
|
||||
scale.X = minXyz;
|
||||
scale.Y = minXyz;
|
||||
scale.Z = minXyz;
|
||||
break;
|
||||
case MaintainRatio.None:
|
||||
break;
|
||||
case MaintainRatio.X_Y:
|
||||
var minXy = Math.Min(scale.X, scale.Y);
|
||||
scale.X = minXy;
|
||||
scale.Y = minXy;
|
||||
break;
|
||||
case MaintainRatio.X_Y_Z:
|
||||
var minXyz = Math.Min(Math.Min(scale.X, scale.Y), scale.Z);
|
||||
scale.X = minXyz;
|
||||
scale.Y = minXyz;
|
||||
scale.Z = minXyz;
|
||||
break;
|
||||
}
|
||||
|
||||
ScaleItem.Matrix = Object3DExtensions.ApplyAtPosition(ScaleItem.Matrix, aabb.Center, Matrix4X4.CreateScale(scale));
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox CalcBoxBounds(AxisAlignedBoundingBox itemAABB)
|
||||
{
|
||||
var center = itemAABB.Center;
|
||||
var minXyz = center - new Vector3(Width / 2, Depth / 2, Height / 2);
|
||||
var maxXyz = center + new Vector3(Width / 2, Depth / 2, Height / 2);
|
||||
return new AxisAlignedBoundingBox(minXyz, maxXyz);
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
|
|
@ -280,16 +288,11 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
|
||||
if (FitType == FitType.Box)
|
||||
{
|
||||
var center = aabb.Center;
|
||||
var worldMatrix = this.WorldMatrix();
|
||||
|
||||
var minXyz = center - new Vector3(Width / 2, Depth / 2, Height / 2);
|
||||
var maxXyz = center + new Vector3(Width / 2, Depth / 2, Height / 2);
|
||||
var bounds = new AxisAlignedBoundingBox(minXyz, maxXyz);
|
||||
// var leftW = Vector3Ex.Transform(, worldMatrix);
|
||||
var right = Vector3Ex.Transform(center + new Vector3(Width / 2, 0, 0), worldMatrix);
|
||||
//var right = Vector3Ex.Transform(center + new Vector3(Width / 2, 0, 0), worldMatrix);
|
||||
// layer.World.Render3DLine(left, right, Agg.Color.Red);
|
||||
layer.World.RenderAabb(bounds, worldMatrix, Agg.Color.Red, 1, 1);
|
||||
layer.World.RenderAabb(CalcBoxBounds(aabb), worldMatrix, Agg.Color.Red, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -301,6 +304,26 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
&& layer.Scene.SelectedItem.DescendantsAndSelf().Where((i) => i == this).Any())
|
||||
{
|
||||
var aabb = ItemToScale.GetAxisAlignedBoundingBox();
|
||||
|
||||
if (FitType == FitType.Box)
|
||||
{
|
||||
return CalcBoxBounds(aabb).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
else
|
||||
{
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(Diameter, Diameter, Height), aabb.Center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public void UpdateControls(PublicPropertyChange change)
|
||||
{
|
||||
change.SetRowVisible(nameof(Diameter), () => FitType != FitType.Box);
|
||||
|
|
|
|||
|
|
@ -133,5 +133,10 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
layer.World.RenderDirectionAxis(new DirectionAxis() { Normal = Axis.Normal, Origin = Vector3.Zero }, this.WorldMatrix(), 30);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return WorldViewExtensions.GetWorldspaceAabbOfRenderDirectionAxis(new DirectionAxis() { Normal = Axis.Normal, Origin = Vector3.Zero }, this.WorldMatrix(), 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,27 +75,54 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
[Description("Split the mesh so it has enough geometry to create a smooth curve")]
|
||||
public bool SplitMesh { get; set; } = true;
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
struct DrawInfo
|
||||
{
|
||||
public AxisAlignedBoundingBox sourceAabb;
|
||||
public double distance;
|
||||
public Vector3 center;
|
||||
}
|
||||
|
||||
DrawInfo GetDrawInfo()
|
||||
{
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var distance = Diameter / 2 + sourceAabb.YSize / 2;
|
||||
var center = sourceAabb.Center + new Vector3(0, BendCcw ? distance : -distance, 0);
|
||||
center.X -= sourceAabb.XSize / 2 - (StartPercent / 100.0) * sourceAabb.XSize;
|
||||
|
||||
return new DrawInfo
|
||||
{
|
||||
sourceAabb = sourceAabb,
|
||||
distance = distance,
|
||||
center = center,
|
||||
};
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
|
||||
// render the top and bottom rings
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), center, Diameter, sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, Diameter, drawInfo.sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
|
||||
// render the split lines
|
||||
var radius = Diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
var xxx = sourceAabb.XSize * (StartPercent / 100.0);
|
||||
var xxx = drawInfo.sourceAabb.XSize * (StartPercent / 100.0);
|
||||
var startAngle = MathHelper.Tau * 3 / 4 - xxx / circumference * MathHelper.Tau;
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), center, Diameter, sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, this.MinSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, Diameter, drawInfo.sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, this.MinSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
var radius = Diameter / 2;
|
||||
var halfHeight = drawInfo.sourceAabb.ZSize / 2;
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(radius, radius, halfHeight), drawInfo.center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
|
|
|||
|
|
@ -102,11 +102,19 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
[DescriptionImage("https://lh3.googleusercontent.com/p9MyKu3AFP55PnobUKZQPqf6iAx11GzXyX-25f1ddrUnfCt8KFGd1YtHOR5HqfO0mhlX2ZVciZV4Yn0Kzfm43SErOS_xzgsESTu9scux")]
|
||||
public DoubleOrExpression MinSidesPerRotation { get; set; } = 30;
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
struct DrawInfo
|
||||
{
|
||||
public double diameter;
|
||||
public double startPercent;
|
||||
public AxisAlignedBoundingBox sourceAabb;
|
||||
public double distance;
|
||||
public Vector3 center;
|
||||
}
|
||||
|
||||
DrawInfo GetDrawInfo()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
var startPercent = StartPercent.Value(this);
|
||||
var minSidesPerRotation = MinSidesPerRotation.Value(this);
|
||||
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var distance = diameter / 2 + sourceAabb.YSize / 2;
|
||||
|
|
@ -114,20 +122,41 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
center.X -= sourceAabb.XSize / 2 - (startPercent / 100.0) * sourceAabb.XSize;
|
||||
center = Vector3.Zero;//.Transform(Matrix.Inverted);
|
||||
|
||||
return new DrawInfo
|
||||
{
|
||||
diameter = diameter,
|
||||
startPercent = startPercent,
|
||||
sourceAabb = sourceAabb,
|
||||
distance = distance,
|
||||
center = center,
|
||||
};
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
var minSidesPerRotation = MinSidesPerRotation.Value(this);
|
||||
|
||||
// render the top and bottom rings
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), center, diameter, sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, drawInfo.diameter, drawInfo.sourceAabb.ZSize, 100, Color.Red, Color.Transparent);
|
||||
|
||||
// render the split lines
|
||||
var radius = diameter / 2;
|
||||
var radius = drawInfo.diameter / 2;
|
||||
var circumference = MathHelper.Tau * radius;
|
||||
var xxx = sourceAabb.XSize * (startPercent / 100.0);
|
||||
var xxx = drawInfo.sourceAabb.XSize * (drawInfo.startPercent / 100.0);
|
||||
var startAngle = MathHelper.Tau * 3 / 4 - xxx / circumference * MathHelper.Tau;
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), center, diameter, sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, minSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
layer.World.RenderCylinderOutline(this.WorldMatrix(), drawInfo.center, drawInfo.diameter, drawInfo.sourceAabb.ZSize, (int)Math.Max(0, Math.Min(100, minSidesPerRotation)), Color.Transparent, Color.Red, phase: startAngle);
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var drawInfo = GetDrawInfo();
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(drawInfo.diameter, drawInfo.diameter, drawInfo.sourceAabb.ZSize), drawInfo.center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
private double DiameterFromAngle()
|
||||
{
|
||||
var diameter = Diameter.Value(this);
|
||||
|
|
|
|||
|
|
@ -150,16 +150,23 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
return fitToBounds;
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
AxisAlignedBoundingBox CalcBounds()
|
||||
{
|
||||
var aabb = UntransformedChildren.GetAxisAlignedBoundingBox();
|
||||
var center = aabb.Center;
|
||||
var worldMatrix = this.WorldMatrix();
|
||||
|
||||
var minXyz = center - new Vector3(SizeX / 2, SizeY / 2, SizeZ / 2);
|
||||
var maxXyz = center + new Vector3(SizeX / 2, SizeY / 2, SizeZ / 2);
|
||||
var bounds = new AxisAlignedBoundingBox(minXyz, maxXyz);
|
||||
layer.World.RenderAabb(bounds, worldMatrix, Color.Red, 1, 1);
|
||||
return new AxisAlignedBoundingBox(minXyz, maxXyz);
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
layer.World.RenderAabb(this.CalcBounds(), this.WorldMatrix(), Color.Red, 1, 1);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.CalcBounds().NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetAxisAlignedBoundingBox(Matrix4X4 matrix)
|
||||
|
|
|
|||
|
|
@ -111,11 +111,10 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
return fitToBounds;
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
AxisAlignedBoundingBox CalcBounds()
|
||||
{
|
||||
var aabb = UntransformedChildren.GetAxisAlignedBoundingBox();
|
||||
var center = aabb.Center;
|
||||
var worldMatrix = this.WorldMatrix();
|
||||
|
||||
var width = Width.Value(this);
|
||||
var depth = Depth.Value(this);
|
||||
|
|
@ -123,8 +122,17 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
|
||||
var minXyz = center - new Vector3(width / 2, depth / 2, height / 2);
|
||||
var maxXyz = center + new Vector3(width / 2, depth / 2, height / 2);
|
||||
var bounds = new AxisAlignedBoundingBox(minXyz, maxXyz);
|
||||
layer.World.RenderAabb(bounds, worldMatrix, Color.Red, 1, 1);
|
||||
return new AxisAlignedBoundingBox(minXyz, maxXyz);
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
layer.World.RenderAabb(this.CalcBounds(), this.WorldMatrix(), Color.Red, 1, 1);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return WorldViewExtensions.GetWorldspaceAabbOfRenderAabb(this.CalcBounds(), this.WorldMatrix(), 1, 1);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetAxisAlignedBoundingBox(Matrix4X4 matrix)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,12 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
// layer.World.RenderCylinderOutline(Matrix4X4.Identity, Vector3.Zero, Diameter, aabb.ZSize, 30, Color.Green);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var aabb = this.WorldAxisAlignedBoundingBox();
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(Diameter, Diameter, aabb.ZSize), aabb.Center);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetAxisAlignedBoundingBox(Matrix4X4 matrix)
|
||||
{
|
||||
if (Children.Count == 2)
|
||||
|
|
|
|||
|
|
@ -214,6 +214,11 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public override bool CanApply => true;
|
||||
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public override bool CanApply => true;
|
||||
|
||||
[HideFromEditor]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ using MatterHackers.DataConverters3D;
|
|||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
|
@ -59,5 +60,10 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -244,6 +244,40 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
}
|
||||
|
||||
public static AxisAlignedBoundingBox GetWorldspaceAabbOfDrawPath(this IObject3D item)
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
if (item is IPathObject pathObject)
|
||||
{
|
||||
if (pathObject.VertexSource == null)
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
var lastPosition = Vector2.Zero;
|
||||
var maxXYZ = item.GetAxisAlignedBoundingBox().MaxXYZ;
|
||||
maxXYZ = maxXYZ.Transform(item.Matrix.Inverted);
|
||||
|
||||
foreach (var vertex in pathObject.VertexSource.Vertices())
|
||||
{
|
||||
var position = vertex.position;
|
||||
|
||||
if (vertex.IsLineTo)
|
||||
{
|
||||
box.ExpandToInclude(new Vector3(lastPosition, maxXYZ.Z + 0.002));
|
||||
box.ExpandToInclude(new Vector3(position, maxXYZ.Z + 0.002));
|
||||
}
|
||||
|
||||
lastPosition = position;
|
||||
}
|
||||
|
||||
return box.NewTransformed(item.WorldMatrix());
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
public static bool IsRoot(this IObject3D object3D)
|
||||
{
|
||||
return object3D.Parent == null;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ using MatterHackers.DataConverters3D;
|
|||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools.Operations
|
||||
{
|
||||
|
|
@ -141,5 +142,10 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,11 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public override bool CanApply => true;
|
||||
|
||||
public override void Apply(UndoBuffer undoBuffer)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ using MatterHackers.DataConverters3D;
|
|||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools.Operations
|
||||
{
|
||||
|
|
@ -158,5 +159,10 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -131,25 +131,42 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
}
|
||||
|
||||
(Vector3, Vector3) GetStartEnd(IPathObject pathObject)
|
||||
{
|
||||
// draw the line that is the rotation point
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
var vertexSource = this.VertexSource.Transform(Matrix);
|
||||
var bounds = vertexSource.GetBounds();
|
||||
var lineX = bounds.Left + AxisPosition.Value(this);
|
||||
|
||||
var start = new Vector3(lineX, aabb.MinXYZ.Y, aabb.MinXYZ.Z);
|
||||
var end = new Vector3(lineX, aabb.MaxXYZ.Y, aabb.MinXYZ.Z);
|
||||
return (start, end);
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
var child = this.Children.FirstOrDefault();
|
||||
if (child is IPathObject pathObject)
|
||||
{
|
||||
// draw the line that is the rotation point
|
||||
var aabb = this.GetAxisAlignedBoundingBox();
|
||||
var vertexSource = this.VertexSource.Transform(Matrix);
|
||||
var bounds = vertexSource.GetBounds();
|
||||
var lineX = bounds.Left + AxisPosition.Value(this);
|
||||
|
||||
var start = new Vector3(lineX, aabb.MinXYZ.Y, aabb.MinXYZ.Z);
|
||||
var end = new Vector3(lineX, aabb.MaxXYZ.Y, aabb.MinXYZ.Z);
|
||||
|
||||
var (start, end) = GetStartEnd(pathObject);
|
||||
layer.World.Render3DLine(start, end, Color.Red, true);
|
||||
layer.World.Render3DLine(start, end, Color.Red.WithAlpha(20), false);
|
||||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var child = this.Children.FirstOrDefault();
|
||||
if (child is IPathObject pathObject)
|
||||
{
|
||||
var (start, end) = GetStartEnd(pathObject);
|
||||
return new AxisAlignedBoundingBox(new Vector3[] { start, end });
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
public bool IsBuilding => this.cancellationToken != null;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ using MatterHackers.DataConverters3D;
|
|||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.VectorMath;
|
||||
using Polygon = System.Collections.Generic.List<ClipperLib.IntPoint>;
|
||||
using Polygons = System.Collections.Generic.List<System.Collections.Generic.List<ClipperLib.IntPoint>>;
|
||||
|
||||
|
|
@ -186,5 +187,10 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +120,18 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
&& layer.Scene.SelectedItem.DescendantsAndSelf().Where((i) => i == this).Any())
|
||||
{
|
||||
return WorldViewExtensions.GetWorldspaceAabbOfRenderDirectionAxis(RotateAbout, this.WorldMatrix(), 30);
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ using MatterHackers.Localizations;
|
|||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
using MatterHackers.VectorMath;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools.Operations
|
||||
|
|
@ -208,6 +209,17 @@ namespace MatterHackers.MatterControl.DesignTools.Operations
|
|||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
if (layer.Scene.SelectedItem != null
|
||||
&& layer.Scene.SelectedItem.DescendantsAndSelf().Where((i) => i == this).Any())
|
||||
{
|
||||
return RenderOpenGl.WorldViewExtensions.GetWorldspaceAabbOfRenderAxis(ScaleAbout, this.WorldMatrix(), 30, 1);
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public async override void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|
|
|
|||
|
|
@ -143,6 +143,14 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
var sourceAabb = this.SourceContainer.GetAxisAlignedBoundingBox();
|
||||
var rotationCenter = SourceContainer.GetSmallestEnclosingCircleAlongZ().Center + RotationOffset;
|
||||
var center = new Vector3(rotationCenter.X, rotationCenter.Y, sourceAabb.Center.Z);
|
||||
return AxisAlignedBoundingBox.CenteredBox(new Vector3(1, 1, sourceAabb.ZSize), center).NewTransformed(this.WorldMatrix());
|
||||
}
|
||||
|
||||
public override Task Rebuild()
|
||||
{
|
||||
this.DebugDepth("Rebuild");
|
||||
|
|
|
|||
|
|
@ -456,17 +456,31 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
}
|
||||
}
|
||||
|
||||
Matrix4X4 CalcTransform()
|
||||
{
|
||||
var aabb = this.GetAxisAlignedBoundingBox(this.WorldMatrix());
|
||||
return this.WorldMatrix() * Matrix4X4.CreateTranslation(0, 0, CalculationHeight.Value(this) - aabb.MinXYZ.Z + ExtrusionHeight.Value(this));
|
||||
}
|
||||
|
||||
public void DrawEditor(Object3DControlsLayer layer, DrawEventArgs e)
|
||||
{
|
||||
if (OutlineIsFromMesh)
|
||||
{
|
||||
var aabb = this.GetAxisAlignedBoundingBox(this.WorldMatrix());
|
||||
// ExtrusionHeight
|
||||
layer.World.RenderPathOutline(this.WorldMatrix() * Matrix4X4.CreateTranslation(0, 0, CalculationHeight.Value(this) - aabb.MinXYZ.Z + ExtrusionHeight.Value(this)), VertexSource, Agg.Color.Red, 5);
|
||||
layer.World.RenderPathOutline(CalcTransform(), VertexSource, Agg.Color.Red, 5);
|
||||
|
||||
// turn the lighting back on
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
if (OutlineIsFromMesh)
|
||||
{
|
||||
// TODO: Untested.
|
||||
return layer.World.GetWorldspaceAabbOfRenderPathOutline(CalcTransform(), VertexSource, 5);
|
||||
}
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -72,6 +72,11 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the actual serialized with that can use expressions
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -264,10 +264,10 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
CreateWidgetIfRequired(controlLayer);
|
||||
markdownWidget.Visible = true;
|
||||
|
||||
var descrpition = Description.Replace("\\n", "\n");
|
||||
if (markdownWidget.Markdown != descrpition)
|
||||
var description = Description.Replace("\\n", "\n");
|
||||
if (markdownWidget.Markdown != description)
|
||||
{
|
||||
markdownWidget.Markdown = descrpition;
|
||||
markdownWidget.Markdown = description;
|
||||
}
|
||||
|
||||
markdownWidget.Width = width;
|
||||
|
|
@ -298,6 +298,11 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
graphics2DOpenGL.RenderTransformedPath(transform, new Ellipse(0, 0, 5, 5), theme.PrimaryAccentColor, false);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
private void CreateWidgetIfRequired(Object3DControlsLayer controlLayer)
|
||||
{
|
||||
if (markdownWidget == null
|
||||
|
|
|
|||
|
|
@ -284,6 +284,11 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return new AxisAlignedBoundingBox(new Vector3[] { worldStartPosition, worldEndPosition });
|
||||
}
|
||||
|
||||
private void CreateWidgetIfRequired(Object3DControlsLayer controlLayer)
|
||||
{
|
||||
if (containerWidget == null
|
||||
|
|
|
|||
|
|
@ -173,5 +173,10 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
theme.TextColor);
|
||||
Mesh.PlaceTextureOnFaces(0, texture);
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -199,5 +199,10 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
{
|
||||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +120,11 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
}
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IObject3DControl.GetWorldspaceAABB()
|
||||
{
|
||||
return shape.GetAxisAlignedBoundingBox().NewTransformed(ShapeMatrix());
|
||||
}
|
||||
|
||||
private Matrix4X4 ShapeMatrix()
|
||||
{
|
||||
var worldPosition = getPosition();
|
||||
|
|
|
|||
|
|
@ -59,5 +59,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
world.RenderDebugAABB(e.Graphics2D, child.GetAxisAlignedBoundingBox());
|
||||
}
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
// No 3D drawing.
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,5 +80,17 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
GLHelper.Render(mesh.mesh, mesh.color);
|
||||
}
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, mesh.mesh.GetAxisAlignedBoundingBox());
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
private bool loadingTextures = false;
|
||||
|
||||
private const int GridSize = 600;
|
||||
|
||||
public FloorDrawable(Object3DControlsLayer.EditorType editorType, ISceneContext sceneContext, Color buildVolumeColor, ThemeConfig theme)
|
||||
{
|
||||
this.buildVolumeColor = buildVolumeColor;
|
||||
|
|
@ -119,7 +121,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
else
|
||||
{
|
||||
int width = 600;
|
||||
int width = GridSize;
|
||||
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
|
|
@ -179,6 +181,34 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
if (!sceneContext.RendererOptions.RenderBed)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
else if (editorType == Object3DControlsLayer.EditorType.Printer)
|
||||
{
|
||||
AxisAlignedBoundingBox box = sceneContext.Mesh != null ? sceneContext.Mesh.GetAxisAlignedBoundingBox() : AxisAlignedBoundingBox.Empty();
|
||||
|
||||
if (sceneContext.PrinterShape != null)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, sceneContext.PrinterShape.GetAxisAlignedBoundingBox());
|
||||
}
|
||||
|
||||
if (sceneContext.BuildVolumeMesh != null && sceneContext.RendererOptions.RenderBuildVolume)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, sceneContext.BuildVolumeMesh.GetAxisAlignedBoundingBox());
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new AxisAlignedBoundingBox(-GridSize, -GridSize, 0, GridSize, GridSize, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBedTexture(IObject3D selectedItem, bool clearToPlaceholderImage = true)
|
||||
{
|
||||
// Early exit for invalid cases
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
using MatterHackers.RenderOpenGl.OpenGl;
|
||||
using MatterHackers.VectorMath;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow
|
||||
{
|
||||
public class FrustumDrawable : IDrawable
|
||||
{
|
||||
public FrustumDrawable()
|
||||
{
|
||||
}
|
||||
|
||||
string IDrawable.Title => "Frustum visualization";
|
||||
|
||||
string IDrawable.Description => "When enabled, captures the current frustum and visualizes it.";
|
||||
|
||||
bool _enabled = false;
|
||||
|
||||
bool IDrawable.Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
meshes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
DrawStage IDrawable.DrawStage => DrawStage.TransparentContent;
|
||||
|
||||
readonly List<(Mesh mesh, Color color)> meshes = new List<(Mesh, Color)>();
|
||||
|
||||
static Mesh GetMesh(Vector3 a, Vector3 b, Vector3 c, Vector3 d)
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.Vertices.Add(a);
|
||||
mesh.Vertices.Add(b);
|
||||
mesh.Vertices.Add(c);
|
||||
mesh.Vertices.Add(d);
|
||||
mesh.Faces.Add(0, 1, 2, mesh.Vertices);
|
||||
mesh.Faces.Add(0, 2, 3, mesh.Vertices);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void IDrawable.Draw(GuiWidget sender, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world)
|
||||
{
|
||||
if (meshes.Count == 0)
|
||||
{
|
||||
Vector3[] ndcCoords = new Vector3[] {
|
||||
new Vector3(+1, +1, +1), // 0: far top right
|
||||
new Vector3(-1, +1, +1), // 1: far top left
|
||||
new Vector3(+1, -1, +1), // 2: far bottom right
|
||||
new Vector3(-1, -1, +1), // 3: far bottom left
|
||||
new Vector3(+1, +1, -1), // 4: near top right
|
||||
new Vector3(-1, +1, -1), // 5: near top left
|
||||
new Vector3(+1, -1, -1), // 6: near bottom right
|
||||
new Vector3(-1, -1, -1), // 7: near bottom left
|
||||
};
|
||||
|
||||
Vector3[] worldspaceCoords = ndcCoords.Select(p => world.NDCToViewspace(p).TransformPosition(world.InverseModelviewMatrix)).ToArray();
|
||||
|
||||
// X
|
||||
meshes.Add((GetMesh(worldspaceCoords[1], worldspaceCoords[5], worldspaceCoords[7], worldspaceCoords[3]), Color.Red.WithAlpha(0.5)));
|
||||
meshes.Add((GetMesh(worldspaceCoords[0], worldspaceCoords[2], worldspaceCoords[6], worldspaceCoords[4]), Color.Red.WithAlpha(0.5)));
|
||||
|
||||
// Y
|
||||
meshes.Add((GetMesh(worldspaceCoords[3], worldspaceCoords[7], worldspaceCoords[6], worldspaceCoords[2]), Color.Green.WithAlpha(0.5)));
|
||||
meshes.Add((GetMesh(worldspaceCoords[1], worldspaceCoords[0], worldspaceCoords[4], worldspaceCoords[5]), Color.Green.WithAlpha(0.5)));
|
||||
|
||||
// Z
|
||||
meshes.Add((GetMesh(worldspaceCoords[0], worldspaceCoords[1], worldspaceCoords[3], worldspaceCoords[2]), Color.Blue.WithAlpha(0.5)));
|
||||
meshes.Add((GetMesh(worldspaceCoords[4], worldspaceCoords[5], worldspaceCoords[7], worldspaceCoords[6]), Color.Blue.WithAlpha(0.5)));
|
||||
}
|
||||
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
GLHelper.Render(mesh.mesh, mesh.color, forceCullBackFaces: false);
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
var box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, mesh.mesh.GetAxisAlignedBoundingBox());
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,5 +43,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
DrawStage DrawStage { get; }
|
||||
|
||||
void Draw(GuiWidget sender, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world);
|
||||
|
||||
/// <returns>The worldspace AABB of the 3D geometry drawn by Draw.</returns>
|
||||
AxisAlignedBoundingBox GetWorldspaceAABB();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,5 +44,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
DrawStage DrawStage { get; }
|
||||
|
||||
void Draw(GuiWidget sender, IObject3D item, bool isSelected, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world);
|
||||
|
||||
/// <returns>The worldspace AABB of the 3D geometry drawn by Draw.</returns>
|
||||
AxisAlignedBoundingBox GetWorldspaceAABB(IObject3D item, bool isSelected, WorldView world);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,5 +72,22 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetWorldspaceAABB(IObject3D item, bool isSelected, WorldView world)
|
||||
{
|
||||
if (item == scene.DebugItem)
|
||||
{
|
||||
AxisAlignedBoundingBox box = WorldViewExtensions.GetWorldspaceAabbOfRenderAabb(item.GetAxisAlignedBoundingBox(), Matrix4X4.Identity, 1);
|
||||
|
||||
if (item.Mesh != null)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, item.Mesh.GetAxisAlignedBoundingBox(item.WorldMatrix()));
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,16 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetWorldspaceAABB(IObject3D item, bool isSelected, WorldView world)
|
||||
{
|
||||
if (isSelected)
|
||||
{
|
||||
return item.GetAxisAlignedBoundingBox();
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
|
||||
public class BvhItemView
|
||||
|
|
|
|||
|
|
@ -88,5 +88,17 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
world.Render3DLineNoPrep(frustum, transformed1, transformed1 + normal, Color.Red, 2);
|
||||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetWorldspaceAABB(IObject3D item, bool isSelected, WorldView world)
|
||||
{
|
||||
if (!isSelected || item.Mesh?.Faces.Count <= 0)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox box = item.Mesh.GetAxisAlignedBoundingBox().NewTransformed(item.WorldMatrix());
|
||||
box.Expand(1); // Normal length.
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
using MatterHackers.RenderOpenGl.OpenGl;
|
||||
using MatterHackers.VectorMath;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow
|
||||
{
|
||||
public class Object3DControlBoundingBoxesDrawable : IDrawable
|
||||
{
|
||||
string IDrawable.Title => "Object3DControlsLayer bounding boxes";
|
||||
|
||||
string IDrawable.Description => "When enabled, show all the bounding boxes reported by Object3DControlsLayer.";
|
||||
|
||||
bool IDrawable.Enabled { get; set; }
|
||||
|
||||
DrawStage IDrawable.DrawStage => DrawStage.TransparentContent;
|
||||
|
||||
void IDrawable.Draw(GuiWidget sender, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world)
|
||||
{
|
||||
if (!(sender is Object3DControlsLayer layer))
|
||||
return;
|
||||
|
||||
GLHelper.PrepareFor3DLineRender(false);
|
||||
|
||||
var frustum = world.GetClippingFrustum();
|
||||
|
||||
Color color = Color.White;
|
||||
|
||||
List<AxisAlignedBoundingBox> aabbs = layer.MakeListOfObjectControlBoundingBoxes();
|
||||
aabbs.Add(layer.GetPrinterNozzleAABB());
|
||||
|
||||
foreach (var box in aabbs)
|
||||
{
|
||||
if (box.XSize < 0)
|
||||
continue;
|
||||
|
||||
Vector3[] v = box.GetCorners();
|
||||
|
||||
Tuple<Vector3, Vector3>[] lines = new Tuple<Vector3, Vector3>[]{
|
||||
new Tuple<Vector3, Vector3>(v[0], v[1]),
|
||||
new Tuple<Vector3, Vector3>(v[2], v[3]),
|
||||
new Tuple<Vector3, Vector3>(v[0], v[3]),
|
||||
new Tuple<Vector3, Vector3>(v[1], v[2]),
|
||||
new Tuple<Vector3, Vector3>(v[4 + 0], v[4 + 1]),
|
||||
new Tuple<Vector3, Vector3>(v[4 + 2], v[4 + 3]),
|
||||
new Tuple<Vector3, Vector3>(v[4 + 0], v[4 + 3]),
|
||||
new Tuple<Vector3, Vector3>(v[4 + 1], v[4 + 2]),
|
||||
new Tuple<Vector3, Vector3>(v[0], v[4 + 0]),
|
||||
new Tuple<Vector3, Vector3>(v[1], v[4 + 1]),
|
||||
new Tuple<Vector3, Vector3>(v[2], v[4 + 2]),
|
||||
new Tuple<Vector3, Vector3>(v[3], v[4 + 3]),
|
||||
};
|
||||
|
||||
foreach (var (start, end) in lines)
|
||||
{
|
||||
world.Render3DLineNoPrep(frustum, start, end, color);
|
||||
//e.Graphics2D.DrawLine(color, world.GetScreenPosition(start), world.GetScreenPosition(end));
|
||||
}
|
||||
}
|
||||
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
// Let's not recurse on this...
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -71,5 +71,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
Object3DControlsLayer.RenderBounds(e, world, bvhIterator);
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
// No 3D drawing.
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,35 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetWorldspaceAABB(IObject3D item, bool isSelected, WorldView world)
|
||||
{
|
||||
if (isSelected && scene.DrawSelection)
|
||||
{
|
||||
var scaleMatrix = CalcScaleMatrix(world, item);
|
||||
return item.Mesh.GetAxisAlignedBoundingBox(scaleMatrix);
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
private Matrix4X4 CalcScaleMatrix(WorldView world, IObject3D item)
|
||||
{
|
||||
// Expand the object
|
||||
var worldMatrix = item.WorldMatrix();
|
||||
var worldBounds = item.Mesh.GetAxisAlignedBoundingBox(worldMatrix);
|
||||
var worldCenter = worldBounds.Center;
|
||||
double distBetweenPixelsWorldSpace = world.GetWorldUnitsPerScreenPixelAtPosition(worldCenter);
|
||||
var pixelsAccross = worldBounds.Size / distBetweenPixelsWorldSpace;
|
||||
var pixelsWant = pixelsAccross + Vector3.One * 4 * Math.Sqrt(2);
|
||||
|
||||
var wantMm = pixelsWant * distBetweenPixelsWorldSpace;
|
||||
|
||||
return worldMatrix.ApplyAtPosition(worldCenter, Matrix4X4.CreateScale(
|
||||
wantMm.X / worldBounds.XSize,
|
||||
wantMm.Y / worldBounds.YSize,
|
||||
wantMm.Z / worldBounds.ZSize));
|
||||
}
|
||||
|
||||
private void RenderSelection(IObject3D item, Color selectionColor, WorldView world)
|
||||
{
|
||||
if (item.Mesh == null)
|
||||
|
|
@ -121,19 +150,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
GL.CullFace(CullFaceMode.Front);
|
||||
|
||||
// Expand the object
|
||||
var worldMatrix = item.WorldMatrix();
|
||||
var worldBounds = item.Mesh.GetAxisAlignedBoundingBox(worldMatrix);
|
||||
var worldCenter = worldBounds.Center;
|
||||
double distBetweenPixelsWorldSpace = world.GetWorldUnitsPerScreenPixelAtPosition(worldCenter);
|
||||
var pixelsAccross = worldBounds.Size / distBetweenPixelsWorldSpace;
|
||||
var pixelsWant = pixelsAccross + Vector3.One * 4 * Math.Sqrt(2);
|
||||
|
||||
var wantMm = pixelsWant * distBetweenPixelsWorldSpace;
|
||||
|
||||
var scaleMatrix = worldMatrix.ApplyAtPosition(worldCenter, Matrix4X4.CreateScale(
|
||||
wantMm.X / worldBounds.XSize,
|
||||
wantMm.Y / worldBounds.YSize,
|
||||
wantMm.Z / worldBounds.ZSize));
|
||||
var scaleMatrix = CalcScaleMatrix(world, item);
|
||||
|
||||
GLHelper.Render(item.Mesh,
|
||||
selectionColor,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
return;
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateType)
|
||||
{
|
||||
if ((invalidateType.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
return;
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public override async void OnInvalidate(InvalidateArgs invalidateArgs)
|
||||
{
|
||||
if ((invalidateArgs.InvalidateType.HasFlag(InvalidateType.Children)
|
||||
|
|
|
|||
|
|
@ -64,6 +64,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.View3D
|
|||
this.DrawPath();
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetEditorWorldspaceAABB(Object3DControlsLayer layer)
|
||||
{
|
||||
return this.GetWorldspaceAabbOfDrawPath();
|
||||
}
|
||||
|
||||
public void AddObject3DControls(Object3DControlsLayer object3DControlsLayer)
|
||||
{
|
||||
object3DControlsLayer.AddControls(ControlTypes.Standard2D);
|
||||
|
|
|
|||
|
|
@ -122,12 +122,16 @@ namespace MatterHackers.MatterControl
|
|||
printerBed.Vertices[i] = printerBed.Vertices[i] - new Vector3Float(-printer.Bed.BedCenter, zTop + .02);
|
||||
}
|
||||
|
||||
printerBed.MarkAsChanged();
|
||||
|
||||
if (buildVolume != null)
|
||||
{
|
||||
for (int i = 0; i < buildVolume.Vertices.Count; i++)
|
||||
{
|
||||
buildVolume.Vertices[i] = buildVolume.Vertices[i] - new Vector3Float(-printer.Bed.BedCenter, zTop + .02);
|
||||
}
|
||||
|
||||
buildVolume.MarkAsChanged();
|
||||
}
|
||||
|
||||
return (printerBed, buildVolume);
|
||||
|
|
|
|||
758
MatterControlLib/PartPreviewWindow/View3D/CameraFittingUtil.cs
Normal file
758
MatterControlLib/PartPreviewWindow/View3D/CameraFittingUtil.cs
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
// This isn't necessary for now as the orthographic camera is always an infinite distance away from the scene.
|
||||
//#define ENABLE_ORTHOGRAPHIC_CAMERA_POSITIONING_ALONG_Z
|
||||
|
||||
//#define ENABLE_PERSPECTIVE_FITTING_DEBUG_DUMP
|
||||
|
||||
// If not defined, orthographic near/far fitting will use Mesh.Split.
|
||||
// If defined, use the same method as perspective near/far fitting. Represent the AABB as solid shapes instead of faces.
|
||||
//#define USE_TETRAHEDRON_CUTTING_FOR_ORTHOGRAPHIC_NEAR_FAR_FITTING
|
||||
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.VectorMath;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace MatterHackers
|
||||
{
|
||||
// For "zoom to selection" and dynamic near/far.
|
||||
internal static class CameraFittingUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// This proportion, scaled by the smaller dimension of the viewport, is subtracted from each side of the viewport for fitting.
|
||||
/// Exposed for testing.
|
||||
/// </summary>
|
||||
public const double MarginScale = 0.1;
|
||||
|
||||
public enum EPerspectiveFittingAlgorithm
|
||||
{
|
||||
/// <summary>
|
||||
/// Has a margin, but does not use MarginScale.
|
||||
/// </summary>
|
||||
TrialAndError,
|
||||
|
||||
/// <summary>
|
||||
/// Fit the camera to the AABB's bounding sphere.
|
||||
/// Guarantees a centered AABB center, but will not center the screenspace AABB.
|
||||
/// </summary>
|
||||
Sphere,
|
||||
|
||||
/// <summary>
|
||||
/// Place the camera at the center of the AABB, then push the camera back until all points are visible.
|
||||
/// Guarantees a centered AABB center, but will not center the screenspace AABB.
|
||||
/// </summary>
|
||||
CenterOnWorldspaceAABB,
|
||||
|
||||
/// <summary>
|
||||
/// Fit the camera to the viewspace AABB, which will tend to be larger than the worldspace AABB.
|
||||
/// Guarantees a centered AABB center, but will not center the screenspace AABB.
|
||||
/// </summary>
|
||||
CenterOnViewspaceAABB,
|
||||
|
||||
/// <summary>
|
||||
/// Take the perspective side planes and fit them around the AABB.
|
||||
/// There will be two intersection lines and the camera will be placed on the one further back.
|
||||
/// Either X or Y will be restrained to one value, and the other will take the value closest to the center of the AABB.
|
||||
/// https://stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene/66113254#66113254
|
||||
/// </summary>
|
||||
IntersectionOfBoundingPlanesWithApproxCentering,
|
||||
|
||||
/// <summary>
|
||||
/// This is the only one that guarantees perfect screenspace centering.
|
||||
/// A solver is used to center the screenspace AABB on the non-restrained axis.
|
||||
/// But this means that the viewspace center will not always be at the center of the screen, so the object can sometimes look off-centered.
|
||||
/// </summary>
|
||||
IntersectionOfBoundingPlanesWithPerfectCentering,
|
||||
}
|
||||
|
||||
// Exposed for testing.
|
||||
// "static readonly" to silence unreachable code warnings.
|
||||
public static readonly EPerspectiveFittingAlgorithm PerspectiveFittingAlgorithm = EPerspectiveFittingAlgorithm.CenterOnWorldspaceAABB;
|
||||
|
||||
#if ENABLE_ORTHOGRAPHIC_CAMERA_POSITIONING_ALONG_Z
|
||||
// Scaled by the box's Z size in viewspace.
|
||||
// If camera Z translation is needed and far - near > threshold, the camera will be placed close to the object.
|
||||
const double OrthographicLargeZRangeScaledThreshold = 3.0;
|
||||
const double OrthographicLargeZRangeScaledDistanceBetweenNearAndObject = 1.0;
|
||||
#endif
|
||||
|
||||
public struct Result
|
||||
{
|
||||
public Vector3 CameraPosition;
|
||||
public double OrthographicViewspaceHeight;
|
||||
}
|
||||
|
||||
public static Result ComputeOrthographicCameraFit(WorldView world, double centerOffsetX, double zNear, double zFar, AxisAlignedBoundingBox worldspaceAABB)
|
||||
{
|
||||
Vector3[] worldspacePoints = worldspaceAABB.GetCorners();
|
||||
Vector3[] viewspacePoints = worldspacePoints.Select(x => x.TransformPosition(world.ModelviewMatrix)).ToArray();
|
||||
|
||||
Vector3 viewspaceCenter = worldspaceAABB.Center.TransformPosition(world.ModelviewMatrix);
|
||||
AxisAlignedBoundingBox viewspaceAABB = new AxisAlignedBoundingBox(viewspaceCenter, viewspaceCenter);
|
||||
foreach (Vector3 point in viewspacePoints)
|
||||
{
|
||||
viewspaceAABB.ExpandToInclude(point);
|
||||
}
|
||||
|
||||
// Take the viewport with margins subtracted, then fit the viewspace AABB to it.
|
||||
Vector2 viewportSize = new Vector2(world.Width + centerOffsetX, world.Height);
|
||||
double baseDim = Math.Min(viewportSize.X, viewportSize.Y);
|
||||
double absTotalMargin = baseDim * MarginScale * 2;
|
||||
Vector2 reducedViewportSize = viewportSize - new Vector2(absTotalMargin, absTotalMargin);
|
||||
double unitsPerPixelX = viewspaceAABB.XSize / reducedViewportSize.X;
|
||||
double unitsPerPixelY = viewspaceAABB.YSize / reducedViewportSize.Y;
|
||||
double unitsPerPixel = Math.Max(unitsPerPixelX, unitsPerPixelY);
|
||||
Vector2 targetViewspaceSize = viewportSize * unitsPerPixel;
|
||||
|
||||
Vector3 viewspaceNearCenter = new Vector3(viewspaceAABB.Center.Xy, viewspaceAABB.MaxXYZ.Z);
|
||||
Vector3 viewspaceCameraPosition = viewspaceNearCenter;
|
||||
|
||||
#if ENABLE_ORTHOGRAPHIC_CAMERA_POSITIONING_ALONG_Z
|
||||
Vector3 viewspaceFarCenter = new Vector3(viewspaceAABB.Center.Xy, viewspaceAABB.MinXYZ.Z);
|
||||
if (-viewspaceNearCenter.Z >= zNear && -viewspaceFarCenter.Z <= zFar)
|
||||
{
|
||||
// The object fits in the Z range without translating along Z.
|
||||
viewspaceCameraPosition.Z = 0;
|
||||
}
|
||||
else if (viewspaceAABB.ZSize * OrthographicLargeZRangeScaledThreshold < zFar - zNear)
|
||||
{
|
||||
// There's lots of Z range.
|
||||
// Place the camera close to the object such that there's a reasonable amount of Z space behind and in front of the object.
|
||||
viewspaceCameraPosition.Z += zNear + viewspaceAABB.ZSize * OrthographicLargeZRangeScaledDistanceBetweenNearAndObject;
|
||||
}
|
||||
else if (viewspaceAABB.ZSize < zFar - zNear)
|
||||
{
|
||||
// There's not much Z range, but enough to contain the object.
|
||||
// Place the camera such that the object is in the middle of the Z range.
|
||||
viewspaceCameraPosition.Z = viewspaceAABB.Center.Z + (zFar - zNear) * 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The object is too big to fit in the Z range.
|
||||
// Place the camera at the near side of the object.
|
||||
viewspaceCameraPosition.Z = viewspaceAABB.MaxXYZ.Z;
|
||||
}
|
||||
#endif
|
||||
|
||||
Vector3 worldspaceCameraPosition = viewspaceCameraPosition.TransformPosition(world.InverseModelviewMatrix);
|
||||
return new Result { CameraPosition = worldspaceCameraPosition, OrthographicViewspaceHeight = targetViewspaceSize.Y };
|
||||
}
|
||||
|
||||
public static Result ComputePerspectiveCameraFit(WorldView world, double centerOffsetX, AxisAlignedBoundingBox worldspaceAABB)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(!world.IsOrthographic);
|
||||
|
||||
Vector3[] worldspacePoints = worldspaceAABB.GetCorners();
|
||||
Vector3[] viewspacePoints = worldspacePoints.Select(p => p.TransformPosition(world.ModelviewMatrix)).ToArray();
|
||||
Vector3 viewspaceCenter = viewspacePoints.Aggregate((a, b) => a + b) / viewspacePoints.Length;
|
||||
|
||||
// Construct a temp WorldView with a smaller FOV to give the resulting view a margin.
|
||||
Vector2 viewportSize = world.ViewportSize + new Vector2(centerOffsetX, 0);
|
||||
double margin = MarginScale * Math.Min(viewportSize.X, viewportSize.Y);
|
||||
WorldView reducedWorld = new WorldView(viewportSize.X - margin * 2, viewportSize.Y - margin * 2);
|
||||
double reducedVFOVRadians = Math.Atan(reducedWorld.Height / world.Height * (world.NearPlaneHeightInViewspace / 2) / world.NearZ) * 2;
|
||||
|
||||
reducedWorld.CalculatePerspectiveMatrixOffCenter(
|
||||
reducedWorld.Width, reducedWorld.Height,
|
||||
0,
|
||||
1, 2, // Arbitrary
|
||||
MathHelper.RadiansToDegrees(reducedVFOVRadians)
|
||||
);
|
||||
|
||||
Plane[] viewspacePlanes = Frustum.FrustumFromProjectionMatrix(reducedWorld.ProjectionMatrix).Planes.Take(4).ToArray();
|
||||
|
||||
Vector3 viewspaceCameraPosition;
|
||||
|
||||
switch (PerspectiveFittingAlgorithm)
|
||||
{
|
||||
case EPerspectiveFittingAlgorithm.TrialAndError:
|
||||
return new Result { CameraPosition = TryPerspectiveCameraFitByIterativeAdjust(world, centerOffsetX, worldspaceAABB) };
|
||||
case EPerspectiveFittingAlgorithm.Sphere:
|
||||
default:
|
||||
viewspaceCameraPosition = PerspectiveCameraFitToSphere(reducedWorld, viewspaceCenter, viewspacePoints);
|
||||
break;
|
||||
case EPerspectiveFittingAlgorithm.CenterOnWorldspaceAABB:
|
||||
viewspaceCameraPosition = PerspectiveCameraFitAlongAxisThroughCenter(viewspacePlanes, viewspaceCenter, viewspacePoints);
|
||||
break;
|
||||
case EPerspectiveFittingAlgorithm.CenterOnViewspaceAABB:
|
||||
viewspaceCameraPosition = PerspectiveCameraFitToViewspaceAABB(viewspacePlanes, viewspaceCenter, viewspacePoints);
|
||||
break;
|
||||
case EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithApproxCentering:
|
||||
viewspaceCameraPosition = PerspectiveCameraFitByAxisAlignedPlaneIntersections(reducedWorld, viewspacePlanes, viewspaceCenter, viewspacePoints, false);
|
||||
break;
|
||||
case EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithPerfectCentering:
|
||||
viewspaceCameraPosition = PerspectiveCameraFitByAxisAlignedPlaneIntersections(reducedWorld, viewspacePlanes, viewspaceCenter, viewspacePoints, true);
|
||||
break;
|
||||
}
|
||||
|
||||
return new Result { CameraPosition = viewspaceCameraPosition.TransformPosition(world.InverseModelviewMatrix) };
|
||||
}
|
||||
|
||||
static bool NeedsToBeSmaller(RectangleDouble partScreenBounds, RectangleDouble goalBounds)
|
||||
{
|
||||
if (partScreenBounds.Bottom < goalBounds.Bottom
|
||||
|| partScreenBounds.Top > goalBounds.Top
|
||||
|| partScreenBounds.Left < goalBounds.Left
|
||||
|| partScreenBounds.Right > goalBounds.Right)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Original code relocated from https://github.com/MatterHackers/MatterControl/blob/e5967ff858f2844734e4802a6c6c8ac973ad92d1/MatterControlLib/PartPreviewWindow/View3D/View3DWidget.cs
|
||||
static Vector3 TryPerspectiveCameraFitByIterativeAdjust(WorldView world, double centerOffsetX, AxisAlignedBoundingBox worldspaceAABB)
|
||||
{
|
||||
var aabb = worldspaceAABB;
|
||||
var center = aabb.Center;
|
||||
// pan to the center
|
||||
var screenCenter = new Vector2(world.Width / 2 + centerOffsetX / 2, world.Height / 2);
|
||||
var centerRay = world.GetRayForLocalBounds(screenCenter);
|
||||
|
||||
// make the target size a portion of the total size
|
||||
var goalBounds = new RectangleDouble(0, 0, world.Width, world.Height);
|
||||
goalBounds.Inflate(-world.Width * .1);
|
||||
|
||||
int rescaleAttempts = 0;
|
||||
var testWorld = new WorldView(world.Width, world.Height);
|
||||
testWorld.RotationMatrix = world.RotationMatrix;
|
||||
var distance = 80.0;
|
||||
|
||||
void AjustDistance()
|
||||
{
|
||||
testWorld.TranslationMatrix = world.TranslationMatrix;
|
||||
var delta = centerRay.origin + centerRay.directionNormal * distance - center;
|
||||
testWorld.Translate(delta);
|
||||
}
|
||||
|
||||
AjustDistance();
|
||||
|
||||
while (rescaleAttempts++ < 500)
|
||||
{
|
||||
|
||||
var partScreenBounds = testWorld.GetScreenBounds(aabb);
|
||||
|
||||
if (NeedsToBeSmaller(partScreenBounds, goalBounds))
|
||||
{
|
||||
distance++;
|
||||
AjustDistance();
|
||||
partScreenBounds = testWorld.GetScreenBounds(aabb);
|
||||
|
||||
// If it crossed over the goal reduct the amount we are adjusting by.
|
||||
if (!NeedsToBeSmaller(partScreenBounds, goalBounds))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
distance--;
|
||||
AjustDistance();
|
||||
partScreenBounds = testWorld.GetScreenBounds(aabb);
|
||||
|
||||
// If it crossed over the goal reduct the amount we are adjusting by.
|
||||
if (NeedsToBeSmaller(partScreenBounds, goalBounds))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TrackballTumbleWidget.AnimateTranslation(center, centerRay.origin + centerRay.directionNormal * distance);
|
||||
// zoom to fill the view
|
||||
// viewControls3D.NotifyResetView();
|
||||
|
||||
return world.EyePosition - ((centerRay.origin + centerRay.directionNormal * distance) - center);
|
||||
}
|
||||
|
||||
static Vector3 PerspectiveCameraFitToSphere(WorldView world, Vector3 viewspaceCenter, Vector3[] viewspacePoints)
|
||||
{
|
||||
double radius = viewspacePoints.Select(p => (p - viewspaceCenter).Length).Max();
|
||||
double distForBT = radius / Math.Sin(MathHelper.DegreesToRadians(world.VFovDegrees) / 2);
|
||||
double distForLR = radius / Math.Sin(MathHelper.DegreesToRadians(world.HFovDegrees) / 2);
|
||||
double distForN = radius + WorldView.PerspectiveProjectionMinimumNearZ;
|
||||
double dist = Math.Max(Math.Max(distForBT, distForLR), distForN);
|
||||
return viewspaceCenter + new Vector3(0, 0, dist);
|
||||
}
|
||||
|
||||
|
||||
static Vector3 PerspectiveCameraFitAlongAxisThroughCenter(Plane[] viewspacePlanes, Vector3 viewspaceCenter, Vector3[] viewspacePoints)
|
||||
{
|
||||
Vector3 viewspaceCameraPosition = viewspaceCenter;
|
||||
|
||||
viewspacePoints = viewspacePoints.Select(p => p - viewspaceCameraPosition).ToArray();
|
||||
|
||||
double relZ = double.NegativeInfinity;
|
||||
|
||||
foreach (Plane viewspacePlane in viewspacePlanes)
|
||||
{
|
||||
relZ = Math.Max(relZ, viewspacePoints.Select(
|
||||
p => p.Z - (viewspacePlane.DistanceFromOrigin - viewspacePlane.Normal.Dot(new Vector3(p.X, p.Y, 0))) / viewspacePlane.Normal.Z
|
||||
).Max());
|
||||
}
|
||||
|
||||
return viewspaceCameraPosition + new Vector3(0, 0, relZ);
|
||||
}
|
||||
|
||||
static Vector3 PerspectiveCameraFitToViewspaceAABB(Plane[] viewspacePlanes, Vector3 viewspaceCenter, Vector3[] viewspacePoints)
|
||||
{
|
||||
AxisAlignedBoundingBox aabb = new AxisAlignedBoundingBox(viewspacePoints);
|
||||
return PerspectiveCameraFitAlongAxisThroughCenter(viewspacePlanes, aabb.Center, aabb.GetCorners());
|
||||
}
|
||||
|
||||
struct Line
|
||||
{
|
||||
public double gradient;
|
||||
public double refX;
|
||||
|
||||
public double YAt(double x) => gradient * (x - refX);
|
||||
|
||||
public Line NegatedY()
|
||||
{
|
||||
return new Line { gradient = -gradient, refX = refX };
|
||||
}
|
||||
}
|
||||
|
||||
static double GetIntersectX(Line a, Line b, double minX, double maxX)
|
||||
{
|
||||
double x = (a.gradient * a.refX - b.gradient * b.refX) / (a.gradient - b.gradient);
|
||||
if (x < minX)
|
||||
return minX;
|
||||
else if (x < maxX)
|
||||
return x;
|
||||
else // or, infinity, NaN
|
||||
return double.PositiveInfinity;
|
||||
}
|
||||
|
||||
struct PiecewiseSegment
|
||||
{
|
||||
public Vector2 start;
|
||||
public Line line;
|
||||
|
||||
public PiecewiseSegment NegatedY()
|
||||
{
|
||||
return new PiecewiseSegment { start = new Vector2(start.X, -start.Y), line = line.NegatedY() };
|
||||
}
|
||||
}
|
||||
|
||||
static List<PiecewiseSegment> SweepDescendingMaxY(double minX, double maxX, Line[] lines)
|
||||
{
|
||||
// Find the piecewise maximum of all the lines.
|
||||
// NOTE: Monotonic decreasing Y, monotonic increasing gradient, max Y at minX.
|
||||
|
||||
bool startsBelow(PiecewiseSegment seg, Line line) => seg.start.Y < line.YAt(seg.start.X);
|
||||
|
||||
// Order segments by gradient, steep to shallow.
|
||||
// For each line:
|
||||
// Discard segments of the piecewise function that start below the line.
|
||||
// Intersect and append a new segment.
|
||||
|
||||
Array.Sort(lines, (a, b) => a.gradient.CompareTo(b.gradient));
|
||||
|
||||
var output = new List<PiecewiseSegment>();
|
||||
|
||||
foreach (Line line in lines)
|
||||
{
|
||||
while (output.Count >= 1 && startsBelow(output.Last(), line))
|
||||
{
|
||||
output.RemoveAt(output.Count - 1);
|
||||
}
|
||||
|
||||
if (output.Count == 0)
|
||||
{
|
||||
output.Add(new PiecewiseSegment { start = new Vector2(minX, line.YAt(minX)), line = line });
|
||||
}
|
||||
else
|
||||
{
|
||||
double x = GetIntersectX(output.Last().line, line, output.Last().start.X, maxX);
|
||||
if (output.Last().start.X < x && x < maxX)
|
||||
output.Add(new PiecewiseSegment { start = new Vector2(x, line.YAt(x)), line = line });
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static List<PiecewiseSegment> SweepDescendingMinY(double minX, double maxX, Line[] lines)
|
||||
{
|
||||
// Negate X and Y.
|
||||
|
||||
// -f(-x) = gradient * (x - refX)
|
||||
// -f(x) = gradient * (-x - refX)
|
||||
// f(x) = gradient * (x + refX)
|
||||
|
||||
List<PiecewiseSegment> segs = SweepDescendingMaxY(-maxX, -minX, lines.Select(line => new Line
|
||||
{
|
||||
gradient = line.gradient,
|
||||
refX = -line.refX
|
||||
}).ToArray()).Select(seg => new PiecewiseSegment
|
||||
{
|
||||
start = -seg.start,
|
||||
line = new Line { gradient = seg.line.gradient, refX = -seg.line.refX }
|
||||
}).Reverse().ToList();
|
||||
|
||||
// The segs above have end points instead of start points. Shift them and set the start point.
|
||||
for (int i = segs.Count - 1; i > 0; --i)
|
||||
{
|
||||
segs[i] = new PiecewiseSegment { start = segs[i - 1].start, line = segs[i].line };
|
||||
}
|
||||
|
||||
segs[0] = new PiecewiseSegment { start = new Vector2(minX, segs[0].line.YAt(minX)), line = segs[0].line };
|
||||
|
||||
return segs;
|
||||
}
|
||||
|
||||
static Vector3 PerspectiveCameraFitByAxisAlignedPlaneIntersections(
|
||||
WorldView world, Plane[] viewspacePlanes, Vector3 viewspaceCenter, Vector3[] viewspacePoints,
|
||||
bool useSolver)
|
||||
{
|
||||
Plane[] viewspaceBoundingPlanes = viewspacePlanes.Select(plane => new Plane(
|
||||
plane.Normal,
|
||||
viewspacePoints.Select(point => plane.Normal.Dot(point)).Min()
|
||||
)).ToArray();
|
||||
|
||||
double maxViewspaceZ = viewspacePoints.Select(p => p.Z).Max();
|
||||
|
||||
// Axis-aligned plane intersection as 2D line intersection: [a, b].[x or y, z] + c = 0
|
||||
Vector3 viewspaceLPlane2D = new Vector3(viewspaceBoundingPlanes[0].Normal.X, viewspaceBoundingPlanes[0].Normal.Z, -viewspaceBoundingPlanes[0].DistanceFromOrigin);
|
||||
Vector3 viewspaceRPlane2D = new Vector3(viewspaceBoundingPlanes[1].Normal.X, viewspaceBoundingPlanes[1].Normal.Z, -viewspaceBoundingPlanes[1].DistanceFromOrigin);
|
||||
Vector3 viewspaceBPlane2D = new Vector3(viewspaceBoundingPlanes[2].Normal.Y, viewspaceBoundingPlanes[2].Normal.Z, -viewspaceBoundingPlanes[2].DistanceFromOrigin);
|
||||
Vector3 viewspaceTPlane2D = new Vector3(viewspaceBoundingPlanes[3].Normal.Y, viewspaceBoundingPlanes[3].Normal.Z, -viewspaceBoundingPlanes[3].DistanceFromOrigin);
|
||||
|
||||
Vector3 intersectionLRInXZ = viewspaceLPlane2D.Cross(viewspaceRPlane2D);
|
||||
Vector3 intersectionBTInYZ = viewspaceBPlane2D.Cross(viewspaceTPlane2D);
|
||||
intersectionLRInXZ.Xy /= intersectionLRInXZ.Z;
|
||||
intersectionBTInYZ.Xy /= intersectionBTInYZ.Z;
|
||||
|
||||
double maxZByPlaneIntersections = Math.Max(intersectionLRInXZ.Y, intersectionBTInYZ.Y);
|
||||
double maxZByNearPlane = maxViewspaceZ + WorldView.PerspectiveProjectionMinimumNearZ;
|
||||
|
||||
// Initial position, before adjustment.
|
||||
Vector3 viewspaceCameraPosition = new Vector3(intersectionLRInXZ.X, intersectionBTInYZ.X, Math.Max(maxZByPlaneIntersections, maxZByNearPlane));
|
||||
|
||||
double optimiseAxis(int axis, double min, double max)
|
||||
{
|
||||
if (!useSolver)
|
||||
{
|
||||
// Pick a point closest to viewspaceCenter.
|
||||
return Math.Min(Math.Max(viewspaceCenter[axis], min), max);
|
||||
}
|
||||
|
||||
// [camX, camY, camZ] = viewspaceCameraPosition (the initial guess, with the final Z)
|
||||
// ndcX = m[1,1] / (z - camZ) * (x - camX)
|
||||
// ndcY = m[2,2] / (z - camZ) * (y - camY)
|
||||
|
||||
Line[] ndcLines = viewspacePoints.Select(viewspacePoint => new Line
|
||||
{
|
||||
gradient = world.ProjectionMatrix[axis, axis] / (viewspacePoint.Z - viewspaceCameraPosition.Z),
|
||||
refX = viewspacePoint[axis]
|
||||
}).ToArray();
|
||||
|
||||
List<PiecewiseSegment> piecewiseMax = SweepDescendingMaxY(min, max, ndcLines);
|
||||
List<PiecewiseSegment> piecewiseMin = SweepDescendingMinY(min, max, ndcLines);
|
||||
|
||||
#if ENABLE_PERSPECTIVE_FITTING_DEBUG_DUMP
|
||||
using (var file = new StreamWriter("perspective centering.csv"))
|
||||
{
|
||||
foreach (Line line in ndcLines)
|
||||
{
|
||||
double ndcAtMin = line.gradient * (min - line.refX);
|
||||
double ndcAtMax = line.gradient * (max - line.refX);
|
||||
file.WriteLine("{0}, {1}", min, ndcAtMin);
|
||||
file.WriteLine("{0}, {1}", max, ndcAtMax);
|
||||
}
|
||||
|
||||
file.WriteLine("");
|
||||
|
||||
foreach (PiecewiseSegment seg in piecewiseMax)
|
||||
file.WriteLine("{0}, {1}", seg.start.X, seg.start.Y);
|
||||
file.WriteLine("{0}, {1}", max, piecewiseMax.Last().line.gradient * (max - piecewiseMax.Last().line.refX));
|
||||
|
||||
file.WriteLine("");
|
||||
|
||||
foreach (PiecewiseSegment seg in piecewiseMin)
|
||||
file.WriteLine("{0}, {1}", seg.start.X, seg.start.Y);
|
||||
file.WriteLine("{0}, {1}", max, piecewiseMin.Last().line.gradient * (max - piecewiseMin.Last().line.refX));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Now, with the piecewise min and max functions, determine the X at which max == -min.
|
||||
// Max is decreasing, -min is increasing. At some point, they should cross over.
|
||||
|
||||
// Cross-over cannot be before minX.
|
||||
if (piecewiseMax[0].start.Y <= -piecewiseMin[0].start.Y)
|
||||
{
|
||||
return min;
|
||||
}
|
||||
|
||||
int maxI = 0;
|
||||
int minI = 0;
|
||||
|
||||
double? resultX = null;
|
||||
|
||||
#if ENABLE_PERSPECTIVE_FITTING_DEBUG_DUMP
|
||||
using (var file = new StreamWriter("perspective piecewise crossover.csv"))
|
||||
#endif
|
||||
{
|
||||
while (maxI < piecewiseMax.Count && minI < piecewiseMin.Count)
|
||||
{
|
||||
PiecewiseSegment maxSeg = piecewiseMax[maxI];
|
||||
PiecewiseSegment minSeg = piecewiseMin[minI].NegatedY();
|
||||
double maxSegEndX = maxI + 1 < piecewiseMax.Count ? piecewiseMax[maxI + 1].start.X : max;
|
||||
double minSegEndX = minI + 1 < piecewiseMin.Count ? piecewiseMin[minI + 1].start.X : max;
|
||||
double sectionMinX = Math.Max(maxSeg.start.X, minSeg.start.X);
|
||||
double sectionMaxX = Math.Min(maxSegEndX, minSegEndX);
|
||||
double crossoverX = GetIntersectX(maxSeg.line, minSeg.line, sectionMinX, sectionMaxX);
|
||||
|
||||
#if ENABLE_PERSPECTIVE_FITTING_DEBUG_DUMP
|
||||
file.WriteLine("{0}, {1}, {2}, {3}", sectionMinX, maxSeg.line.YAt(sectionMinX), sectionMinX, minSeg.line.YAt(sectionMinX));
|
||||
#endif
|
||||
|
||||
if (crossoverX < sectionMaxX && !resultX.HasValue)
|
||||
{
|
||||
resultX = crossoverX;
|
||||
#if !ENABLE_PERSPECTIVE_FITTING_DEBUG_DUMP
|
||||
return resultX.Value;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (maxSegEndX < minSegEndX)
|
||||
{
|
||||
++maxI;
|
||||
}
|
||||
else
|
||||
{
|
||||
++minI;
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_PERSPECTIVE_FITTING_DEBUG_DUMP
|
||||
file.WriteLine("{0}, {1}, {2}, {3}", max, piecewiseMax.Last().line.YAt(max), max, piecewiseMin.Last().NegatedY().line.YAt(max));
|
||||
#endif
|
||||
}
|
||||
|
||||
return resultX ?? max;
|
||||
}
|
||||
|
||||
// Two axes are restrained to a single value. The last has a range of valid values.
|
||||
if (intersectionLRInXZ.Y < intersectionBTInYZ.Y)
|
||||
{
|
||||
// The camera will be on the intersection of the top/bottom planes.
|
||||
// The left/right planes in front intersect with the horizontal line and determine the limits of X.
|
||||
double minX = (viewspaceRPlane2D.Y * intersectionBTInYZ.Y + viewspaceRPlane2D.Z) / -viewspaceRPlane2D.X;
|
||||
double maxX = (viewspaceLPlane2D.Y * intersectionBTInYZ.Y + viewspaceLPlane2D.Z) / -viewspaceLPlane2D.X;
|
||||
viewspaceCameraPosition.X = optimiseAxis(0, minX, maxX);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The camera will be on the intersection of the left/right planes.
|
||||
// The top/bottom planes in front intersect with the vertical line and determine the limits of Y.
|
||||
double minY = (viewspaceTPlane2D.Y * intersectionLRInXZ.Y + viewspaceTPlane2D.Z) / -viewspaceTPlane2D.X;
|
||||
double maxY = (viewspaceBPlane2D.Y * intersectionLRInXZ.Y + viewspaceBPlane2D.Z) / -viewspaceBPlane2D.X;
|
||||
viewspaceCameraPosition.Y = optimiseAxis(1, minY, maxY);
|
||||
}
|
||||
|
||||
return viewspaceCameraPosition;
|
||||
}
|
||||
|
||||
/// Tetrahedron-based AABB-frustum intersection code for perspective projection dynamic near/far planes.
|
||||
/// Temporary, may be replaced with a method that fits to individual triangles if needed.
|
||||
/// This tetrahedron clipping is only used if
|
||||
/// ENABLE_PERSPECTIVE_PROJECTION_DYNAMIC_NEAR_FAR is defined in View3DWidget.cs, or
|
||||
/// USE_TETRAHEDRON_CUTTING_FOR_ORTHOGRAPHIC_NEAR_FAR_FITTING is defined.
|
||||
|
||||
struct Tetrahedron
|
||||
{
|
||||
public Vector3 a, b, c, d;
|
||||
}
|
||||
|
||||
static Tetrahedron[] ClipTetrahedron(Tetrahedron T, Plane plane)
|
||||
{
|
||||
// true iff inside
|
||||
Vector3[] vs = new Vector3[] { T.a, T.b, T.c, T.d };
|
||||
bool[] sides = vs.Select(v => plane.GetDistanceFromPlane(v) > 0).ToArray();
|
||||
int numInside = sides.Count(b => b);
|
||||
|
||||
Vector3 temp;
|
||||
|
||||
switch (numInside)
|
||||
{
|
||||
case 0:
|
||||
default:
|
||||
return new Tetrahedron[] { };
|
||||
|
||||
case 1:
|
||||
{
|
||||
int i = Array.IndexOf(sides, true);
|
||||
(vs[0], vs[i]) = (vs[i], vs[0]);
|
||||
temp = vs[0]; plane.ClipLine(ref temp, ref vs[1]);
|
||||
temp = vs[0]; plane.ClipLine(ref temp, ref vs[2]);
|
||||
temp = vs[0]; plane.ClipLine(ref temp, ref vs[3]);
|
||||
// One tetra inside.
|
||||
return new Tetrahedron[] {
|
||||
new Tetrahedron{ a = vs[0], b = vs[1], c = vs[2], d = vs[3] },
|
||||
};
|
||||
}
|
||||
|
||||
case 2:
|
||||
{
|
||||
int i = Array.IndexOf(sides, true);
|
||||
(vs[0], vs[i]) = (vs[i], vs[0]);
|
||||
(sides[0], sides[i]) = (sides[i], sides[0]);
|
||||
int j = Array.IndexOf(sides, true, 1);
|
||||
(vs[1], vs[j]) = (vs[j], vs[1]);
|
||||
Vector3 v02 = vs[2];
|
||||
Vector3 v03 = vs[3];
|
||||
Vector3 v12 = vs[2];
|
||||
Vector3 v13 = vs[3];
|
||||
temp = vs[0]; plane.ClipLine(ref temp, ref v02);
|
||||
temp = vs[0]; plane.ClipLine(ref temp, ref v03);
|
||||
temp = vs[1]; plane.ClipLine(ref temp, ref v12);
|
||||
temp = vs[1]; plane.ClipLine(ref temp, ref v13);
|
||||
// Three new tetra sharing the common edge v03-v12.
|
||||
return new Tetrahedron[] {
|
||||
new Tetrahedron{ a = v12, b = v03, c = vs[0], d = v02 },
|
||||
new Tetrahedron{ a = v12, b = v03, c = vs[0], d = vs[1] },
|
||||
new Tetrahedron{ a = v12, b = v03, c = vs[1], d = v13 },
|
||||
};
|
||||
}
|
||||
|
||||
case 3:
|
||||
{
|
||||
int i = Array.IndexOf(sides, false);
|
||||
(vs[3], vs[i]) = (vs[i], vs[3]);
|
||||
Vector3 v03 = vs[3];
|
||||
Vector3 v13 = vs[3];
|
||||
Vector3 v23 = vs[3];
|
||||
temp = vs[0]; plane.ClipLine(ref temp, ref v03);
|
||||
temp = vs[1]; plane.ClipLine(ref temp, ref v13);
|
||||
temp = vs[2]; plane.ClipLine(ref temp, ref v23);
|
||||
// Three new tetra.
|
||||
return new Tetrahedron[] {
|
||||
new Tetrahedron{ a = vs[0], b = v03, c = v13, d = v23 },
|
||||
new Tetrahedron{ a = vs[0], b = vs[1], c = v13, d = v23 },
|
||||
new Tetrahedron{ a = vs[0], b = vs[1], c = vs[2], d = v23 },
|
||||
};
|
||||
}
|
||||
|
||||
case 4:
|
||||
return new Tetrahedron[] { T };
|
||||
}
|
||||
}
|
||||
|
||||
static readonly Tetrahedron[] BoxOfTetras = new Func<Tetrahedron[]>(() =>
|
||||
{
|
||||
Vector3[] corners = new Vector3[] {
|
||||
new Vector3(+1, +1, +1), // [0]
|
||||
new Vector3(-1, +1, +1), // [1]
|
||||
new Vector3(+1, -1, +1), // [2]
|
||||
new Vector3(-1, -1, +1), // [3]
|
||||
new Vector3(+1, +1, -1), // [4]
|
||||
new Vector3(-1, +1, -1), // [5]
|
||||
new Vector3(+1, -1, -1), // [6]
|
||||
new Vector3(-1, -1, -1), // [7]
|
||||
};
|
||||
|
||||
// All the tetras share a common diagonal edge.
|
||||
var box = new Tetrahedron[] {
|
||||
new Tetrahedron{ a = corners[0], b = corners[7], c = corners[5], d = corners[4] },
|
||||
new Tetrahedron{ a = corners[0], b = corners[7], c = corners[4], d = corners[6] },
|
||||
new Tetrahedron{ a = corners[0], b = corners[7], c = corners[6], d = corners[2] },
|
||||
new Tetrahedron{ a = corners[0], b = corners[7], c = corners[2], d = corners[3] },
|
||||
new Tetrahedron{ a = corners[0], b = corners[7], c = corners[3], d = corners[1] },
|
||||
new Tetrahedron{ a = corners[0], b = corners[7], c = corners[1], d = corners[5] },
|
||||
};
|
||||
|
||||
// Sanity check.
|
||||
double V = box.Select(T => Math.Abs((T.a - T.d).Dot((T.b - T.d).Cross(T.c - T.d)))).Sum();
|
||||
System.Diagnostics.Debug.Assert(MathHelper.AlmostEqual(V, 2 * 2 * 2 * 6, 1e-5));
|
||||
|
||||
return box;
|
||||
})();
|
||||
|
||||
static Tetrahedron[] MakeAABBTetraArray(AxisAlignedBoundingBox box)
|
||||
{
|
||||
Vector3 halfsize = box.Size * 0.5;
|
||||
Vector3 center = box.Center;
|
||||
return BoxOfTetras.Select(T => new Tetrahedron
|
||||
{
|
||||
a = center + T.a * halfsize,
|
||||
b = center + T.b * halfsize,
|
||||
c = center + T.c * halfsize,
|
||||
d = center + T.d * halfsize,
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
static Tetrahedron[] ClipTetras(Tetrahedron[] tetra, Plane plane)
|
||||
{
|
||||
return tetra.SelectMany(T => ClipTetrahedron(T, plane)).ToArray();
|
||||
}
|
||||
|
||||
public static Tuple<double, double> ComputeNearFarOfClippedWorldspaceAABB(bool isOrthographic, Plane[] worldspacePlanes, Matrix4X4 worldToViewspace, AxisAlignedBoundingBox worldspceAABB)
|
||||
{
|
||||
if (worldspceAABB == null || worldspceAABB.XSize < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
#if !USE_TETRAHEDRON_CUTTING_FOR_ORTHOGRAPHIC_NEAR_FAR_FITTING
|
||||
if (isOrthographic)
|
||||
{
|
||||
Mesh mesh = PlatonicSolids.CreateCube(worldspceAABB.Size);
|
||||
mesh.Translate(worldspceAABB.Center);
|
||||
|
||||
double tolerance = 0.001;
|
||||
foreach (Plane plane in worldspacePlanes)
|
||||
{
|
||||
mesh.Split(plane, onPlaneDistance: tolerance, cleanAndMerge: false, discardFacesOnNegativeSide: true);
|
||||
|
||||
// Remove any faces outside the plane (without using discardFacesOnNegativeSide).
|
||||
//for (int i = mesh.Faces.Count - 1; i >= 0; --i)
|
||||
//{
|
||||
// Face face = mesh.Faces[i];
|
||||
// double maxDist = new int[] { face.v0, face.v1, face.v2 }.Select(vi => plane.Normal.Dot(new Vector3(mesh.Vertices[vi]))).Max();
|
||||
// if (maxDist < (plane.DistanceFromOrigin < 0 ? plane.DistanceFromOrigin * (1 - 1e-4) : plane.DistanceFromOrigin * (1 + 1e-4)))
|
||||
// {
|
||||
// mesh.Faces[i] = mesh.Faces[mesh.Faces.Count - 1];
|
||||
// mesh.Faces.RemoveAt(mesh.Faces.Count - 1);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
mesh.CleanAndMerge();
|
||||
|
||||
if (mesh.Vertices.Any())
|
||||
{
|
||||
mesh.Transform(worldToViewspace);
|
||||
var depths = mesh.Vertices.Select(v => -v.Z);
|
||||
return Tuple.Create<double, double>(depths.Min(), depths.Max());
|
||||
}
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// The above works for orthographic, but won't for perspective as the planes aren't parallel to Z.
|
||||
// So, cut some tetrahedra instead.
|
||||
|
||||
Tetrahedron[] tetras = MakeAABBTetraArray(worldspceAABB);
|
||||
foreach (Plane plane in worldspacePlanes)
|
||||
{
|
||||
tetras = ClipTetras(tetras, plane);
|
||||
}
|
||||
|
||||
if (tetras.Any())
|
||||
{
|
||||
var vertices = tetras.SelectMany(T => new Vector3[] { T.a, T.b, T.c, T.d });
|
||||
var depths = vertices.Select(v => -v.TransformPosition(worldToViewspace).Z).ToArray();
|
||||
return Tuple.Create<double, double>(depths.Min(), depths.Max());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
Object3DControlContext.GuiSurface.BeforeDraw -= Object3DControl_BeforeDraw;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
bool ShouldDrawMoveControls()
|
||||
{
|
||||
bool shouldDrawMoveControls = true;
|
||||
if (Object3DControlContext.SelectedObject3DControl != null
|
||||
|
|
@ -164,6 +164,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
{
|
||||
shouldDrawMoveControls = false;
|
||||
}
|
||||
return shouldDrawMoveControls;
|
||||
}
|
||||
|
||||
public override void Draw(DrawGlContentEventArgs e)
|
||||
{
|
||||
bool shouldDrawMoveControls = ShouldDrawMoveControls();
|
||||
|
||||
var selectedItem = RootSelection;
|
||||
if (selectedItem != null)
|
||||
|
|
@ -234,7 +240,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
if (MouseDownOnControl && hitPlane != null)
|
||||
{
|
||||
IntersectInfo info = hitPlane.GetClosestIntersection(mouseEvent3D.MouseRay);
|
||||
IntersectInfo info = hitPlane.GetClosestIntersectionWithinRayDistanceRange(mouseEvent3D.MouseRay);
|
||||
|
||||
if (info != null
|
||||
&& selectedItem != null
|
||||
|
|
@ -342,5 +348,24 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
bool shouldDrawScaleControls = ShouldDrawMoveControls();
|
||||
var selectedItem = RootSelection;
|
||||
|
||||
if (selectedItem != null)
|
||||
{
|
||||
if (shouldDrawScaleControls)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, upArrowMesh.GetAxisAlignedBoundingBox().NewTransformed(TotalTransform));
|
||||
}
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,20 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
base.Draw(e);
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
var selectedItem = RootSelection;
|
||||
if (selectedItem != null
|
||||
&& Object3DControlContext.Scene.ShowSelectionShadow)
|
||||
{
|
||||
AxisAlignedBoundingBox selectedBounds = selectedItem.GetAxisAlignedBoundingBox();
|
||||
var withScale = Matrix4X4.CreateScale(selectedBounds.XSize, selectedBounds.YSize, 1) * TotalTransform;
|
||||
return GetNormalShadowMesh().GetAxisAlignedBoundingBox().NewTransformed(withScale);
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
// no widgets allocated so nothing to close
|
||||
|
|
|
|||
|
|
@ -139,6 +139,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
public override AxisAlignedBoundingBox GetWorldspaceAABB()
|
||||
{
|
||||
// No 3D drawing.
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
|
||||
public override void CancelOperation()
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ either expressed or implied, of the FreeBSD Project.
|
|||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.RayTracer;
|
||||
using MatterHackers.VectorMath;
|
||||
using System;
|
||||
|
||||
namespace MatterHackers.MeshVisualizer
|
||||
|
|
@ -65,6 +66,9 @@ namespace MatterHackers.MeshVisualizer
|
|||
bool DrawOnTop { get; }
|
||||
|
||||
void Draw(DrawGlContentEventArgs e);
|
||||
|
||||
/// <returns>The worldspace AABB of the 3D geometry drawn by Draw.</returns>
|
||||
AxisAlignedBoundingBox GetWorldspaceAABB();
|
||||
|
||||
ITraceable GetTraceable();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,5 +166,8 @@ namespace MatterHackers.MeshVisualizer
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <returns>The worldspace AABB of the 3D geometry drawn by Draw.</returns>
|
||||
public abstract AxisAlignedBoundingBox GetWorldspaceAABB();
|
||||
}
|
||||
}
|
||||
|
|
@ -80,5 +80,15 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
darkWireframe);
|
||||
}
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
if (levelingDataMesh != null)
|
||||
{
|
||||
return levelingDataMesh.GetAxisAlignedBoundingBox();
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -667,6 +667,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
{
|
||||
new AxisIndicatorDrawable(),
|
||||
new ScreenspaceAxisIndicatorDrawable(),
|
||||
new FrustumDrawable(),
|
||||
new Object3DControlBoundingBoxesDrawable(),
|
||||
new SceneTraceDataDrawable(sceneContext),
|
||||
new AABBDrawable(sceneContext),
|
||||
new LevelingDataDrawable(sceneContext),
|
||||
|
|
@ -1030,6 +1032,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
return bCenterInViewSpace.LengthSquared.CompareTo(aCenterInViewSpace.LengthSquared);
|
||||
}
|
||||
|
||||
private Matrix4X4 GetEmulatorNozzleTransform()
|
||||
{
|
||||
var emulator = (PrinterEmulator.Emulator)sceneContext.Printer.Connection.serialPort;
|
||||
return Matrix4X4.CreateTranslation(emulator.CurrentPosition + new Vector3(.5, .5, 5));
|
||||
}
|
||||
|
||||
private HashSet<IObject3D> editorDrawItems = new HashSet<IObject3D>();
|
||||
private void DrawGlContent(DrawEventArgs e)
|
||||
{
|
||||
|
|
@ -1095,7 +1103,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
var matrix = Matrix4X4.CreateTranslation(emulator.CurrentPosition + new Vector3(.5, .5, 5));
|
||||
var matrix = GetEmulatorNozzleTransform();
|
||||
GLHelper.Render(emulatorNozzleMesh,
|
||||
MaterialRendering.Color(sceneContext.Printer, emulator.ExtruderIndex),
|
||||
matrix,
|
||||
|
|
@ -1255,5 +1263,71 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
WireframeAndSolid,
|
||||
None
|
||||
}
|
||||
|
||||
public List<AxisAlignedBoundingBox> MakeListOfObjectControlBoundingBoxes()
|
||||
{
|
||||
var selectedItem = scene.SelectedItem;
|
||||
|
||||
var aabbs = new List<AxisAlignedBoundingBox>(100);
|
||||
|
||||
foreach (var ctrl in Object3DControls)
|
||||
{
|
||||
aabbs.Add(ctrl.GetWorldspaceAABB());
|
||||
}
|
||||
|
||||
if (selectedItem is IEditorDraw editorDraw)
|
||||
{
|
||||
aabbs.Add(editorDraw.GetEditorWorldspaceAABB(this));
|
||||
}
|
||||
|
||||
foreach (var ctrl in scene.Descendants())
|
||||
{
|
||||
if (ctrl is ICustomEditorDraw customEditorDraw1 && customEditorDraw1.DoEditorDraw(ctrl == selectedItem))
|
||||
if (ctrl is IEditorDraw editorDraw2)
|
||||
aabbs.Add(editorDraw2.GetEditorWorldspaceAABB(this));
|
||||
}
|
||||
|
||||
foreach (var ctrl in drawables)
|
||||
{
|
||||
if (ctrl.Enabled)
|
||||
aabbs.Add(ctrl.GetWorldspaceAABB());
|
||||
}
|
||||
|
||||
foreach (var obj in scene.Children)
|
||||
{
|
||||
if (obj.Visible)
|
||||
{
|
||||
foreach (var item in obj.VisibleMeshes())
|
||||
{
|
||||
bool isSelected = selectedItem != null
|
||||
&& (item == selectedItem
|
||||
|| item.Parents().Any(p => p == selectedItem));
|
||||
|
||||
// Invoke all item Drawables
|
||||
foreach (var drawable in itemDrawables)
|
||||
{
|
||||
if (drawable.Enabled)
|
||||
{
|
||||
aabbs.Add(drawable.GetWorldspaceAABB(item, isSelected, this.World));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aabbs.Add(floorDrawable.GetWorldspaceAABB());
|
||||
|
||||
return aabbs;
|
||||
}
|
||||
|
||||
public AxisAlignedBoundingBox GetPrinterNozzleAABB()
|
||||
{
|
||||
if (sceneContext.Printer?.Connection?.serialPort is PrinterEmulator.Emulator emulator)
|
||||
{
|
||||
return emulatorNozzleMesh.GetAxisAlignedBoundingBox().NewTransformed(GetEmulatorNozzleTransform());
|
||||
}
|
||||
|
||||
return AxisAlignedBoundingBox.Empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,11 +35,17 @@ using MatterHackers.VectorMath;
|
|||
using MatterHackers.VectorMath.TrackBall;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow
|
||||
{
|
||||
public class TrackballTumbleWidgetExtended : GuiWidget
|
||||
{
|
||||
private const double PerspectiveMinZoomDist = 3;
|
||||
private const double PerspectiveMaxZoomDist = 2300;
|
||||
private const double OrthographicMinZoomViewspaceHeight = 0.01;
|
||||
private const double OrthographicMaxZoomViewspaceHeight = 1000;
|
||||
|
||||
public NearFarAction GetNearFar;
|
||||
private readonly MotionQueue motionQueue = new MotionQueue();
|
||||
private readonly GuiWidget sourceWidget;
|
||||
|
|
@ -65,9 +71,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
this.world = world;
|
||||
this.sourceWidget = sourceWidget;
|
||||
this.Object3DControlLayer = Object3DControlLayer;
|
||||
this.PerspectiveMode = !this.world.IsOrthographic;
|
||||
}
|
||||
|
||||
public delegate void NearFarAction(out double zNear, out double zFar);
|
||||
public delegate void NearFarAction(WorldView world, out double zNear, out double zFar);
|
||||
|
||||
public double CenterOffsetX
|
||||
{
|
||||
|
|
@ -86,13 +93,55 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
public TrackBallController TrackBallController { get; }
|
||||
public TrackBallTransformType TransformState { get; set; }
|
||||
public double ZoomDelta { get; set; } = 0.2f;
|
||||
public double OrthographicZoomScalingFactor { get; set; } = 1.2f;
|
||||
public bool TurntableEnabled { get; set; }
|
||||
public bool PerspectiveMode { get; set; } = true;
|
||||
public bool PerspectiveMode { get; private set; }
|
||||
// Projection mode switch animations will capture this value. When this is changed, those animations will cease to have an effect.
|
||||
UInt64 _perspectiveModeSwitchAnimationSerialNumber = 0;
|
||||
Action _perspectiveModeSwitchFinishAnimation = null;
|
||||
|
||||
public void ChangeProjectionMode(bool perspective, bool animate)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
if (PerspectiveMode == perspective)
|
||||
return;
|
||||
|
||||
PerspectiveMode = perspective;
|
||||
|
||||
if (!PerspectiveMode)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(!this.world.IsOrthographic);
|
||||
if (!this.world.IsOrthographic)
|
||||
{
|
||||
// Perspective -> Orthographic
|
||||
DoSwitchToProjectionMode(true, GetWorldRefPositionForProjectionSwitch(), animate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(this.world.IsOrthographic);
|
||||
if (this.world.IsOrthographic)
|
||||
{
|
||||
// Orthographic -> Perspective
|
||||
DoSwitchToProjectionMode(false, GetWorldRefPositionForProjectionSwitch(), animate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void FinishProjectionSwitch()
|
||||
{
|
||||
++_perspectiveModeSwitchAnimationSerialNumber;
|
||||
_perspectiveModeSwitchFinishAnimation?.Invoke();
|
||||
_perspectiveModeSwitchFinishAnimation = null;
|
||||
}
|
||||
|
||||
public void DoRotateAroundOrigin(Vector2 mousePosition)
|
||||
{
|
||||
if (isRotating)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
Quaternion activeRotationQuaternion;
|
||||
if (TurntableEnabled)
|
||||
{
|
||||
|
|
@ -131,24 +180,29 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
public override void OnDraw(Graphics2D graphics2D)
|
||||
{
|
||||
RecalculateProjection();
|
||||
bool wantRefPositionVisibleForTransformInteraction = TrackBallController.CurrentTrackingType == TrackBallTransformType.None && (
|
||||
TransformState == TrackBallTransformType.Translation ||
|
||||
TransformState == TrackBallTransformType.Rotation
|
||||
);
|
||||
|
||||
if (TrackBallController.CurrentTrackingType == TrackBallTransformType.None)
|
||||
bool isSwitchingProjectionMode = _perspectiveModeSwitchFinishAnimation != null;
|
||||
|
||||
if (isSwitchingProjectionMode || wantRefPositionVisibleForTransformInteraction)
|
||||
{
|
||||
switch (TransformState)
|
||||
{
|
||||
case TrackBallTransformType.Translation:
|
||||
case TrackBallTransformType.Rotation:
|
||||
var circle = new Ellipse(world.GetScreenPosition(mouseDownWorldPosition), 8 * DeviceScale);
|
||||
graphics2D.Render(new Stroke(circle, 2 * DeviceScale), theme.PrimaryAccentColor);
|
||||
graphics2D.Render(new Stroke(new Stroke(circle, 4 * DeviceScale), DeviceScale), theme.TextColor.WithAlpha(128));
|
||||
break;
|
||||
}
|
||||
var circle = new Ellipse(world.GetScreenPosition(mouseDownWorldPosition), 8 * DeviceScale);
|
||||
graphics2D.Render(new Stroke(circle, 2 * DeviceScale), theme.PrimaryAccentColor);
|
||||
graphics2D.Render(new Stroke(new Stroke(circle, 4 * DeviceScale), DeviceScale), theme.TextColor.WithAlpha(128));
|
||||
}
|
||||
|
||||
|
||||
base.OnDraw(graphics2D);
|
||||
}
|
||||
|
||||
public void OnBeforeDraw3D()
|
||||
{
|
||||
RecalculateProjection();
|
||||
}
|
||||
|
||||
public void OnDraw3D()
|
||||
{
|
||||
if (hitPlane != null)
|
||||
|
|
@ -278,6 +332,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
private void ZoomToMousePosition(Vector2 mousePosition, double zoomDelta)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
var rayAtScreenCenter = world.GetRayForLocalBounds(new Vector2(Width / 2, Height / 2));
|
||||
var rayAtMousePosition = world.GetRayForLocalBounds(mousePosition);
|
||||
IntersectInfo intersectionInfo = Object3DControlLayer.Scene.GetBVHData().GetClosestIntersection(rayAtMousePosition);
|
||||
|
|
@ -293,7 +349,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
// we did not hit anything
|
||||
// find a new 3d mouse position by hitting the screen plane at the distance of the last 3d mouse down position
|
||||
hitPlane = new PlaneShape(new Plane(rayAtScreenCenter.directionNormal, mouseDownWorldPosition), null);
|
||||
intersectionInfo = hitPlane.GetClosestIntersection(rayAtMousePosition);
|
||||
intersectionInfo = hitPlane.GetClosestIntersectionWithinRayDistanceRange(rayAtMousePosition);
|
||||
if (intersectionInfo != null)
|
||||
{
|
||||
ZoomToWorldPosition(intersectionInfo.HitPosition, zoomDelta);
|
||||
|
|
@ -305,28 +361,38 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
public void RecalculateProjection()
|
||||
{
|
||||
double trackingRadius = Math.Min(Width * .45, Height * .45);
|
||||
|
||||
// TODO: Should probably be `Width / 2`, but currently has no effect?
|
||||
TrackBallController.ScreenCenter = new Vector2(Width / 2 - CenterOffsetX, Height / 2);
|
||||
|
||||
TrackBallController.TrackBallRadius = trackingRadius;
|
||||
|
||||
var zNear = .01;
|
||||
var zFar = 100.0;
|
||||
double zNear = WorldView.DefaultNearZ;
|
||||
double zFar = WorldView.DefaultFarZ;
|
||||
|
||||
GetNearFar?.Invoke(out zNear, out zFar);
|
||||
Vector2 newViewportSize = new Vector2(Math.Max(1, sourceWidget.LocalBounds.Width), Math.Max(1, sourceWidget.LocalBounds.Height));
|
||||
|
||||
if (CenterOffsetX != 0)
|
||||
// Update the projection parameters for GetNearFar.
|
||||
// NOTE: PerspectiveMode != this.world.IsOrthographic due to transition animations.
|
||||
if (this.world.IsOrthographic)
|
||||
{
|
||||
this.world.CalculatePerspectiveMatrixOffCenter(sourceWidget.Width, sourceWidget.Height, CenterOffsetX, zNear, zFar);
|
||||
|
||||
if (!PerspectiveMode)
|
||||
{
|
||||
this.world.CalculatePerspectiveMatrixOffCenter(sourceWidget.Width, sourceWidget.Height, CenterOffsetX, zNear, zFar, 2);
|
||||
//this.world.CalculateOrthogrphicMatrixOffCenter(sourceWidget.Width, sourceWidget.Height, CenterOffsetX, zNear, zFar);
|
||||
}
|
||||
this.world.CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(newViewportSize.X, newViewportSize.Y, CenterOffsetX, this.world.NearPlaneHeightInViewspace, zNear, zFar);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.world.CalculatePerspectiveMatrix(sourceWidget.Width, sourceWidget.Height, zNear, zFar);
|
||||
this.world.CalculatePerspectiveMatrixOffCenter(newViewportSize.X, newViewportSize.Y, CenterOffsetX, zNear, zFar, this.world.VFovDegrees);
|
||||
}
|
||||
|
||||
GetNearFar?.Invoke(this.world, out zNear, out zFar);
|
||||
|
||||
// Use the updated near/far planes.
|
||||
if (this.world.IsOrthographic)
|
||||
{
|
||||
this.world.CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(newViewportSize.X, newViewportSize.Y, CenterOffsetX, this.world.NearPlaneHeightInViewspace, zNear, zFar);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.world.CalculatePerspectiveMatrixOffCenter(newViewportSize.X, newViewportSize.Y, CenterOffsetX, zNear, zFar, this.world.VFovDegrees);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -338,6 +404,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
public void SetRotationWithDisplacement(Quaternion rotationQ)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
if (isRotating)
|
||||
{
|
||||
ZeroVelocity();
|
||||
|
|
@ -348,6 +416,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
public void StartRotateAroundOrigin(Vector2 mousePosition)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
if (isRotating)
|
||||
{
|
||||
ZeroVelocity();
|
||||
|
|
@ -360,6 +430,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
public void Translate(Vector2 position)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
if (isRotating)
|
||||
{
|
||||
ZeroVelocity();
|
||||
|
|
@ -368,7 +440,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
if (hitPlane != null)
|
||||
{
|
||||
var rayAtPosition = world.GetRayForLocalBounds(position);
|
||||
var hitAtPosition = hitPlane.GetClosestIntersection(rayAtPosition);
|
||||
var hitAtPosition = hitPlane.GetClosestIntersectionWithinRayDistanceRange(rayAtPosition);
|
||||
|
||||
if (hitAtPosition != null)
|
||||
{
|
||||
|
|
@ -389,10 +461,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
UiThread.ClearInterval(runningInterval);
|
||||
}
|
||||
EndRotateAroundOrigin();
|
||||
FinishProjectionSwitch();
|
||||
}
|
||||
|
||||
public void ZoomToWorldPosition(Vector3 worldPosition, double zoomDelta)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
if (isRotating)
|
||||
{
|
||||
ZeroVelocity();
|
||||
|
|
@ -400,20 +475,94 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
// calculate the vector between the camera and the intersection position and move the camera along it by ZoomDelta, then set it's direction
|
||||
var delta = worldPosition - world.EyePosition;
|
||||
var deltaLength = delta.Length;
|
||||
var minDist = 3;
|
||||
var maxDist = 2300;
|
||||
if ((deltaLength < minDist && zoomDelta < 0)
|
||||
|| (deltaLength > maxDist && zoomDelta > 0))
|
||||
|
||||
if (this.world.IsOrthographic)
|
||||
{
|
||||
return;
|
||||
bool isZoomIn = zoomDelta < 0;
|
||||
double scaleFactor = isZoomIn ? 1 / OrthographicZoomScalingFactor : OrthographicZoomScalingFactor;
|
||||
double newViewspaceHeight = this.world.NearPlaneHeightInViewspace * scaleFactor;
|
||||
|
||||
if (isZoomIn
|
||||
? newViewspaceHeight < OrthographicMinZoomViewspaceHeight
|
||||
: newViewspaceHeight > OrthographicMaxZoomViewspaceHeight)
|
||||
{
|
||||
newViewspaceHeight = this.world.NearPlaneHeightInViewspace;
|
||||
}
|
||||
|
||||
this.world.CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(this.world.Width, this.world.Height, CenterOffsetX,
|
||||
newViewspaceHeight, this.world.NearZ, this.world.FarZ);
|
||||
|
||||
// Zero out the viewspace Z component.
|
||||
delta = delta.TransformVector(this.world.ModelviewMatrix);
|
||||
delta.Z = 0;
|
||||
delta = delta.TransformVector(this.world.InverseModelviewMatrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
var deltaLength = delta.Length;
|
||||
|
||||
if ((deltaLength < PerspectiveMinZoomDist && zoomDelta < 0)
|
||||
|| (deltaLength > PerspectiveMaxZoomDist && zoomDelta > 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var zoomVec = delta * zoomDelta;
|
||||
world.Translate(zoomVec);
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
public void ZoomToAABB(AxisAlignedBoundingBox box)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
if (isRotating)
|
||||
ZeroVelocity();
|
||||
|
||||
if (world.IsOrthographic)
|
||||
{
|
||||
// Using fake values for near/far.
|
||||
// ComputeOrthographicCameraFit may move the camera to wherever as long as the scene is centered, then
|
||||
// GetNearFar will figure out the near/far planes in the next projection update.
|
||||
CameraFittingUtil.Result result = CameraFittingUtil.ComputeOrthographicCameraFit(world, CenterOffsetX, 0, 1, box);
|
||||
|
||||
WorldView tempWorld = new WorldView(world.Width, world.Height);
|
||||
tempWorld.CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(world.Width, world.Height, CenterOffsetX, result.OrthographicViewspaceHeight, 0, 1);
|
||||
double endViewspaceHeight = tempWorld.NearPlaneHeightInViewspace;
|
||||
double startViewspaceHeight = world.NearPlaneHeightInViewspace;
|
||||
|
||||
AnimateOrthographicTranslationAndHeight(
|
||||
world.EyePosition, startViewspaceHeight,
|
||||
result.CameraPosition, endViewspaceHeight
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
CameraFittingUtil.Result result = CameraFittingUtil.ComputePerspectiveCameraFit(world, CenterOffsetX, box);
|
||||
AnimateTranslation(result.CameraPosition, world.EyePosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Used for testing.
|
||||
public RectangleDouble WorldspaceAabbToBottomScreenspaceRectangle(AxisAlignedBoundingBox box)
|
||||
{
|
||||
var points = box.GetCorners().Select(v => this.world.WorldspaceToBottomScreenspace(v).Xy);
|
||||
var rect = new RectangleDouble(points.First(), points.First());
|
||||
foreach (Vector2 v in points.Skip(1))
|
||||
{
|
||||
rect.ExpandToInclude(v);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
// Used for testing.
|
||||
public Vector3 WorldspaceToBottomScreenspace(Vector3 v)
|
||||
{
|
||||
return this.world.WorldspaceToBottomScreenspace(v);
|
||||
}
|
||||
|
||||
private void ApplyVelocity()
|
||||
{
|
||||
if (isRotating)
|
||||
|
|
@ -443,8 +592,15 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
private Vector3 GetWorldRefPositionForProjectionSwitch()
|
||||
{
|
||||
return mouseDownWorldPosition;
|
||||
}
|
||||
|
||||
private void CalculateMouseDownPostionAndPlane(Vector2 mousePosition)
|
||||
{
|
||||
FinishProjectionSwitch();
|
||||
|
||||
var rayAtMousePosition = world.GetRayForLocalBounds(mousePosition);
|
||||
var intersectionInfo = Object3DControlLayer.Scene.GetBVHData().GetClosestIntersection(rayAtMousePosition);
|
||||
var rayAtScreenCenter = world.GetRayForLocalBounds(new Vector2(Width / 2, Height / 2));
|
||||
|
|
@ -459,7 +615,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
// we did not hit anything
|
||||
// find a new 3d mouse position by hitting the screen plane at the distance of the last 3d mouse down position
|
||||
hitPlane = new PlaneShape(new Plane(rayAtScreenCenter.directionNormal, mouseDownWorldPosition), null);
|
||||
intersectionInfo = hitPlane.GetClosestIntersection(rayAtMousePosition);
|
||||
intersectionInfo = hitPlane.GetClosestIntersectionWithinRayDistanceRange(rayAtMousePosition);
|
||||
|
||||
if (intersectionInfo != null)
|
||||
{
|
||||
mouseDownWorldPosition = intersectionInfo.HitPosition;
|
||||
|
|
@ -495,6 +652,103 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}, after);
|
||||
}
|
||||
|
||||
// To orthographic:
|
||||
// Translate the camera towards infinity by maintaining an invariant perspective plane in worldspace and reducing the FOV to zero.
|
||||
// The animation will switch to true orthographic at the end.
|
||||
// To perspective:
|
||||
// Translate the camera from infinity by maintaining an invariant perspective plane in worldspace and increasing the FOV to the default.
|
||||
// The animation will switch out of orthographic in the first frame.
|
||||
private void DoSwitchToProjectionMode(
|
||||
bool toOrthographic,
|
||||
Vector3 worldspaceRefPosition,
|
||||
bool animate)
|
||||
{
|
||||
ZeroVelocity();
|
||||
|
||||
System.Diagnostics.Debug.Assert(toOrthographic != this.world.IsOrthographic); // Starting in the correct projection mode.
|
||||
System.Diagnostics.Debug.Assert(_perspectiveModeSwitchFinishAnimation == null); // No existing animation.
|
||||
|
||||
Matrix4X4 originalViewToWorld = this.world.InverseModelviewMatrix;
|
||||
|
||||
Vector3 viewspaceRefPosition = worldspaceRefPosition.TransformPosition(this.world.ModelviewMatrix);
|
||||
// Don't let this become negative when the ref position is behind the camera.
|
||||
double refPlaneHeightInViewspace = Math.Abs(this.world.GetViewspaceHeightAtPosition(viewspaceRefPosition));
|
||||
double refZ = viewspaceRefPosition.Z;
|
||||
|
||||
double refFOV = MathHelper.DegreesToRadians(
|
||||
toOrthographic
|
||||
? this.world.VFovDegrees // start FOV
|
||||
: WorldView.DefaultPerspectiveVFOVDegrees // end FOV
|
||||
);
|
||||
|
||||
const int numUpdates = 10;
|
||||
|
||||
var update = new Action<int>((i) =>
|
||||
{
|
||||
if (toOrthographic && i >= numUpdates)
|
||||
{
|
||||
world.CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(world.Width, world.Height, CenterOffsetX, refPlaneHeightInViewspace, 0, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
double t = i / (double)numUpdates;
|
||||
double fov = toOrthographic ? refFOV * (1 - t) : refFOV * t;
|
||||
|
||||
double dist = refPlaneHeightInViewspace / 2 / Math.Tan(fov / 2);
|
||||
double eyeZ = refZ + dist;
|
||||
|
||||
Vector3 viewspaceEyePosition = new Vector3(0, 0, eyeZ);
|
||||
|
||||
//System.Diagnostics.Trace.WriteLine("{0} {1} {2}".FormatWith(fovDegrees, dist, eyeZ));
|
||||
|
||||
world.CalculatePerspectiveMatrixOffCenter(world.Width, world.Height, CenterOffsetX, WorldView.DefaultNearZ, WorldView.DefaultFarZ, MathHelper.RadiansToDegrees(fov));
|
||||
world.EyePosition = viewspaceEyePosition.TransformPosition(originalViewToWorld);
|
||||
}
|
||||
});
|
||||
|
||||
if (animate)
|
||||
{
|
||||
_perspectiveModeSwitchFinishAnimation = () =>
|
||||
{
|
||||
update(numUpdates);
|
||||
};
|
||||
|
||||
UInt64 serialNumber = ++_perspectiveModeSwitchAnimationSerialNumber;
|
||||
|
||||
Animation.Run(this, 0.25, numUpdates, (i) =>
|
||||
{
|
||||
if (serialNumber == _perspectiveModeSwitchAnimationSerialNumber)
|
||||
{
|
||||
update(i);
|
||||
if (i >= numUpdates)
|
||||
{
|
||||
_perspectiveModeSwitchFinishAnimation = null;
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
update(numUpdates);
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimateOrthographicTranslationAndHeight(
|
||||
Vector3 startCameraPosition, double startViewspaceHeight,
|
||||
Vector3 endCameraPosition, double endViewspaceHeight,
|
||||
Action after = null)
|
||||
{
|
||||
ZeroVelocity();
|
||||
Animation.Run(this, .25, 10, (update) =>
|
||||
{
|
||||
double t = update / 10.0;
|
||||
world.EyePosition = Vector3.Lerp(startCameraPosition, endCameraPosition, t);
|
||||
// Arbitrary near/far planes. The next projection update will re-fit them.
|
||||
double height = startViewspaceHeight * (1 - t) + endViewspaceHeight * t;
|
||||
world.CalculateOrthogrphicMatrixOffCenterWithViewspaceHeight(world.Width, world.Height, CenterOffsetX, height, 0, 1);
|
||||
}, after);
|
||||
}
|
||||
|
||||
internal class MotionQueue
|
||||
{
|
||||
private readonly List<TimeAndPosition> motionQueue = new List<TimeAndPosition>();
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ The views and conclusions contained in the software and documentation are those
|
|||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
// #define INCLUDE_ORTHOGRAPHIC
|
||||
#define INCLUDE_ORTHOGRAPHIC
|
||||
#define ENABLE_PERSPECTIVE_PROJECTION_DYNAMIC_NEAR_FAR
|
||||
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
|
|
@ -46,6 +47,7 @@ using MatterHackers.MatterControl.Library;
|
|||
using MatterHackers.MatterControl.PrinterCommunication;
|
||||
using MatterHackers.MatterControl.PrinterControls.PrinterConnections;
|
||||
using MatterHackers.MatterControl.SlicerConfiguration;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.RayTracer;
|
||||
using MatterHackers.RenderOpenGl;
|
||||
|
|
@ -66,6 +68,11 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
{
|
||||
public class View3DWidget : GuiWidget, IDrawable
|
||||
{
|
||||
// Padded by this amount on each side, in case of unaccounted for scene geometry.
|
||||
// For orthogrpahic, this is an offset on either side scaled by far - near.
|
||||
// For perspective, this + 1 is used to scale the near and far planes.
|
||||
private const double DynamicNearFarBoundsPaddingFactor = 0.1;
|
||||
|
||||
private bool deferEditorTillMouseUp = false;
|
||||
private bool expandSelection;
|
||||
|
||||
|
|
@ -470,6 +477,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
var zoomToSelectionButton = new IconButton(StaticData.Instance.LoadIcon("select.png", 16, 16).SetToColor(theme.TextColor), theme)
|
||||
{
|
||||
Name = "Zoom to selection button",
|
||||
ToolTipText = "Zoom to Selection".Localize(),
|
||||
Margin = theme.ButtonSpacing
|
||||
};
|
||||
|
|
@ -519,27 +527,29 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
};
|
||||
|
||||
#if INCLUDE_ORTHOGRAPHIC
|
||||
var perspectiveEnabled = UserSettings.Instance.get(UserSettingsKey.PerspectiveMode) != "False";
|
||||
TrackballTumbleWidget.PerspectiveMode = perspectiveEnabled;
|
||||
var perspectiveEnabled = UserSettings.Instance.get(UserSettingsKey.PerspectiveMode) != false.ToString();
|
||||
TrackballTumbleWidget.ChangeProjectionMode(perspectiveEnabled, false);
|
||||
var projectionButton = new RadioIconButton(StaticData.Instance.LoadIcon("perspective.png", 16, 16).SetToColor(theme.TextColor), theme)
|
||||
{
|
||||
Name = "Projection mode button",
|
||||
ToolTipText = "Perspective Mode".Localize(),
|
||||
Margin = theme.ButtonSpacing,
|
||||
ToggleButton = true,
|
||||
SiblingRadioButtonList = new List<GuiWidget>(),
|
||||
Checked = turntableEnabled,
|
||||
Checked = TrackballTumbleWidget.PerspectiveMode,
|
||||
};
|
||||
AddRoundButton(projectionButton, RotatedMargin(projectionButton, -MathHelper.Tau * .3));
|
||||
projectionButton.CheckedStateChanged += (s, e) =>
|
||||
{
|
||||
UserSettings.Instance.set(UserSettingsKey.PerspectiveMode, projectionButton.Checked.ToString());
|
||||
TrackballTumbleWidget.PerspectiveMode = projectionButton.Checked;
|
||||
TrackballTumbleWidget.ChangeProjectionMode(projectionButton.Checked, true);
|
||||
if (true)
|
||||
{
|
||||
// Make sure the view has up going the right direction
|
||||
// WIP, this should fix the current rotation rather than reset the view
|
||||
ResetView();
|
||||
//ResetView();
|
||||
}
|
||||
|
||||
Invalidate();
|
||||
};
|
||||
#endif
|
||||
|
|
@ -672,81 +682,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
public void ZoomToSelection()
|
||||
{
|
||||
bool NeedsToBeSmaller(RectangleDouble partScreenBounds, RectangleDouble goalBounds)
|
||||
{
|
||||
if (partScreenBounds.Bottom < goalBounds.Bottom
|
||||
|| partScreenBounds.Top > goalBounds.Top
|
||||
|| partScreenBounds.Left < goalBounds.Left
|
||||
|| partScreenBounds.Right > goalBounds.Right)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var selectedItem = this.Scene.SelectedItem;
|
||||
if (selectedItem != null)
|
||||
{
|
||||
var aabb = selectedItem.GetAxisAlignedBoundingBox();
|
||||
var center = aabb.Center;
|
||||
// pan to the center
|
||||
var world = sceneContext.World;
|
||||
var screenCenter = new Vector2(world.Width / 2 - selectedObjectPanel.Width / 2, world.Height / 2);
|
||||
var centerRay = world.GetRayForLocalBounds(screenCenter);
|
||||
|
||||
// make the target size a portion of the total size
|
||||
var goalBounds = new RectangleDouble(0, 0, world.Width, world.Height);
|
||||
goalBounds.Inflate(-world.Width * .1);
|
||||
|
||||
int rescaleAttempts = 0;
|
||||
var testWorld = new WorldView(world.Width, world.Height);
|
||||
testWorld.RotationMatrix = world.RotationMatrix;
|
||||
var distance = 80.0;
|
||||
|
||||
void AjustDistance()
|
||||
{
|
||||
testWorld.TranslationMatrix = world.TranslationMatrix;
|
||||
var delta = centerRay.origin + centerRay.directionNormal * distance - center;
|
||||
testWorld.Translate(delta);
|
||||
}
|
||||
|
||||
AjustDistance();
|
||||
|
||||
while (rescaleAttempts++ < 500)
|
||||
{
|
||||
|
||||
var partScreenBounds = testWorld.GetScreenBounds(aabb);
|
||||
|
||||
if (NeedsToBeSmaller(partScreenBounds, goalBounds))
|
||||
{
|
||||
distance++;
|
||||
AjustDistance();
|
||||
partScreenBounds = testWorld.GetScreenBounds(aabb);
|
||||
|
||||
// If it crossed over the goal reduct the amount we are adjusting by.
|
||||
if (!NeedsToBeSmaller(partScreenBounds, goalBounds))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
distance--;
|
||||
AjustDistance();
|
||||
partScreenBounds = testWorld.GetScreenBounds(aabb);
|
||||
|
||||
// If it crossed over the goal reduct the amount we are adjusting by.
|
||||
if (NeedsToBeSmaller(partScreenBounds, goalBounds))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrackballTumbleWidget.AnimateTranslation(center, centerRay.origin + centerRay.directionNormal * distance);
|
||||
// zoom to fill the view
|
||||
// viewControls3D.NotifyResetView();
|
||||
TrackballTumbleWidget.ZoomToAABB(selectedItem.GetAxisAlignedBoundingBox());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -808,46 +747,71 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
};
|
||||
}
|
||||
|
||||
private void GetNearFar(out double zNear, out double zFar)
|
||||
private void GetNearFar(WorldView world, out double zNear, out double zFar)
|
||||
{
|
||||
zNear = .1;
|
||||
zFar = 100;
|
||||
// All but the near and far planes.
|
||||
Plane[] worldspacePlanes = Frustum.FrustumFromProjectionMatrix(world.ProjectionMatrix).Planes.Take(4)
|
||||
.Select(p => p.Transform(world.InverseModelviewMatrix)).ToArray();
|
||||
|
||||
// this function did not fix the image z fighting, so for now I'm just going to return rather than have it run.
|
||||
return;
|
||||
|
||||
var bounds = Scene.GetAxisAlignedBoundingBox();
|
||||
|
||||
if (bounds.XSize > 0)
|
||||
// TODO: (Possibly) Compute a dynamic near plane based on visible triangles rather than clipped AABBs.
|
||||
// Currently, the near and far planes are fit to clipped AABBs in the scene.
|
||||
// A rudimentary implementation to start off with. The significant limitation is that zNear is ~zero when inside an AABB.
|
||||
// The resulting frustum can be visualized using the debug menu.
|
||||
// The far plane is less important.
|
||||
// Other ideas are an infinite far plane, depth clamping, multi-pass rendering, AABB feedback from the previous frame, dedicated scene manager for all 3D rendering.
|
||||
Tuple<double, double> nearFar = null;
|
||||
foreach (var aabb in Object3DControlLayer.MakeListOfObjectControlBoundingBoxes())
|
||||
{
|
||||
zNear = double.PositiveInfinity;
|
||||
zFar = double.NegativeInfinity;
|
||||
ExpandNearAndFarToBounds(ref zNear, ref zFar, bounds);
|
||||
|
||||
// TODO: add in the bed bounds
|
||||
|
||||
// TODO: add in the print volume bounds
|
||||
nearFar = ExpandNearAndFarToClippedBounds(nearFar, world.IsOrthographic, worldspacePlanes, world.ModelviewMatrix, aabb);
|
||||
}
|
||||
nearFar = ExpandNearAndFarToClippedBounds(nearFar, world.IsOrthographic, worldspacePlanes, world.ModelviewMatrix, Object3DControlLayer.GetPrinterNozzleAABB());
|
||||
nearFar = ExpandNearAndFarToClippedBounds(nearFar, world.IsOrthographic, worldspacePlanes, world.ModelviewMatrix, Scene.GetAxisAlignedBoundingBox());
|
||||
|
||||
zNear = nearFar != null ? nearFar.Item1 : WorldView.DefaultNearZ;
|
||||
zFar = nearFar != null ? nearFar.Item2 : WorldView.DefaultFarZ;
|
||||
|
||||
if (world.IsOrthographic)
|
||||
{
|
||||
WorldView.SanitiseOrthographicNearFar(ref zNear, ref zFar);
|
||||
|
||||
// Add some padding in case of unaccounted geometry.
|
||||
double padding = (zFar - zNear) * DynamicNearFarBoundsPaddingFactor;
|
||||
zNear -= padding;
|
||||
zFar += padding;
|
||||
|
||||
WorldView.SanitiseOrthographicNearFar(ref zNear, ref zFar);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if ENABLE_PERSPECTIVE_PROJECTION_DYNAMIC_NEAR_FAR
|
||||
WorldView.SanitisePerspectiveNearFar(ref zNear, ref zFar);
|
||||
|
||||
zNear /= 1 + DynamicNearFarBoundsPaddingFactor;
|
||||
zFar *= 1 + DynamicNearFarBoundsPaddingFactor;
|
||||
#else
|
||||
zNear = WorldView.DefaultNearZ;
|
||||
zFar = WorldView.DefaultFarZ;
|
||||
#endif
|
||||
WorldView.SanitisePerspectiveNearFar(ref zNear, ref zFar);
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Assert(zNear < zFar && zFar < double.PositiveInfinity);
|
||||
}
|
||||
|
||||
private void ExpandNearAndFarToBounds(ref double zNear, ref double zFar, AxisAlignedBoundingBox bounds)
|
||||
private static Tuple<double, double> ExpandNearAndFarToClippedBounds(Tuple<double, double> nearFar, bool isOrthographic, Plane[] worldspacePlanes, Matrix4X4 worldToViewspace, AxisAlignedBoundingBox bounds)
|
||||
{
|
||||
for (int x = 0; x < 2; x++)
|
||||
Tuple<double, double> thisNearFar = CameraFittingUtil.ComputeNearFarOfClippedWorldspaceAABB(isOrthographic, worldspacePlanes, worldToViewspace, bounds);
|
||||
if (nearFar == null)
|
||||
{
|
||||
for (int y = 0; y < 2; y++)
|
||||
{
|
||||
for (int z = 0; z < 2; z++)
|
||||
{
|
||||
var cornerPoint = new Vector3((x == 0) ? bounds.MinXYZ.X : bounds.MaxXYZ.X,
|
||||
(y == 0) ? bounds.MinXYZ.Y : bounds.MaxXYZ.Y,
|
||||
(z == 0) ? bounds.MinXYZ.Z : bounds.MaxXYZ.Z);
|
||||
|
||||
Vector3 viewPosition = cornerPoint.Transform(sceneContext.World.ModelviewMatrix);
|
||||
|
||||
zNear = Math.Max(.1, Math.Min(zNear, -viewPosition.Z));
|
||||
zFar = Math.Max(Math.Max(zFar, -viewPosition.Z), zNear + .1);
|
||||
}
|
||||
}
|
||||
return thisNearFar;
|
||||
}
|
||||
else if (thisNearFar == null)
|
||||
{
|
||||
return nearFar;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Tuple.Create(Math.Min(nearFar.Item1, thisNearFar.Item1), Math.Max(nearFar.Item2, thisNearFar.Item2));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1385,6 +1349,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
TrackballTumbleWidget.OnBeforeDraw3D();
|
||||
|
||||
base.OnDraw(graphics2D);
|
||||
}
|
||||
|
||||
|
|
@ -1784,7 +1750,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
Ray ray = sceneContext.World.GetRayForLocalBounds(localPosition);
|
||||
|
||||
return CurrentSelectInfo.HitPlane.GetClosestIntersection(ray);
|
||||
return CurrentSelectInfo.HitPlane.GetClosestIntersectionWithinRayDistanceRange(ray);
|
||||
}
|
||||
|
||||
public void DragSelectedObject(IObject3D selectedItem, Vector2 localMousePosition)
|
||||
|
|
@ -1801,7 +1767,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
Vector2 meshViewerWidgetScreenPosition = this.Object3DControlLayer.TransformFromParentSpace(this, localMousePosition);
|
||||
Ray ray = sceneContext.World.GetRayForLocalBounds(meshViewerWidgetScreenPosition);
|
||||
|
||||
IntersectInfo info = CurrentSelectInfo.HitPlane.GetClosestIntersection(ray);
|
||||
IntersectInfo info = CurrentSelectInfo.HitPlane.GetClosestIntersectionWithinRayDistanceRange(ray);
|
||||
if (info != null)
|
||||
{
|
||||
if (CurrentSelectInfo.LastMoveDelta == Vector3.PositiveInfinity)
|
||||
|
|
@ -2314,6 +2280,37 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
AxisAlignedBoundingBox IDrawable.GetWorldspaceAABB()
|
||||
{
|
||||
AxisAlignedBoundingBox box = AxisAlignedBoundingBox.Empty();
|
||||
|
||||
if (CurrentSelectInfo.DownOnPart
|
||||
&& TrackballTumbleWidget.TransformState == TrackBallTransformType.None
|
||||
&& Keyboard.IsKeyDown(Keys.ShiftKey))
|
||||
{
|
||||
var drawCenter = CurrentSelectInfo.PlaneDownHitPos;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
box.ExpandToInclude(drawCenter - new Vector3(-50, 0, 0));
|
||||
box.ExpandToInclude(drawCenter - new Vector3(50, 0, 0));
|
||||
box.ExpandToInclude(drawCenter - new Vector3(0, -50, 0));
|
||||
box.ExpandToInclude(drawCenter - new Vector3(0, 50, 0));
|
||||
drawCenter.Z = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Render 3D GCode if applicable
|
||||
if (sceneContext.LoadedGCode != null
|
||||
&& sceneContext.GCodeRenderer != null
|
||||
&& printerTabPage?.Printer.ViewState.ViewMode == PartViewMode.Layers3D)
|
||||
{
|
||||
box = AxisAlignedBoundingBox.Union(box, printerTabPage.Printer.Bed.GetAabbOfRenderGCode3D());
|
||||
}
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
string IDrawable.Title { get; } = "View3DWidget Extensions";
|
||||
|
||||
string IDrawable.Description { get; } = "Render axis indicators for shift drag and 3D GCode view";
|
||||
|
|
|
|||
|
|
@ -89,5 +89,11 @@ namespace MatterHackers.MeshVisualizer
|
|||
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
public static AxisAlignedBoundingBox GetWorldspaceAabbOfRenderDirectionAxis(DirectionAxis axis, Matrix4X4 matrix, double size)
|
||||
{
|
||||
double radius = axis.Normal.Length * size;
|
||||
return AxisAlignedBoundingBox.CenteredHalfExtents(new Vector3(radius, radius, radius), axis.Origin).NewTransformed(matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 7b9b6716605841d83d1ec8dbc4086e8235245896
|
||||
Subproject commit e71e8e1fcffba3de1cac98f2b78d0eb5ccc57178
|
||||
192
Tests/MatterControl.AutomationTests/CameraFittingUtilTests.cs
Normal file
192
Tests/MatterControl.AutomationTests/CameraFittingUtilTests.cs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
using MatterHackers.Agg;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.GuiAutomation;
|
||||
using MatterHackers.MatterControl.PartPreviewWindow;
|
||||
using MatterHackers.VectorMath;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MatterHackers.MatterControl.Tests.Automation
|
||||
{
|
||||
[TestFixture, Category("MatterControl.UI.Automation"), RunInApplicationDomain, Apartment(ApartmentState.STA)]
|
||||
public class CameraFittingUtilTests
|
||||
{
|
||||
private const string CoinName = "MatterControl - Coin.stl";
|
||||
|
||||
static Task DoZoomToSelectionTest(bool ortho, bool wideObject)
|
||||
{
|
||||
return MatterControlUtilities.RunTest(testRunner =>
|
||||
{
|
||||
testRunner.OpenPartTab(removeDefaultPhil: wideObject);
|
||||
|
||||
var view3D = testRunner.GetWidgetByName("View3DWidget", out _) as View3DWidget;
|
||||
var scene = view3D.Object3DControlLayer.Scene;
|
||||
|
||||
if (wideObject)
|
||||
AddCoinToBed(testRunner, scene);
|
||||
|
||||
if (ortho)
|
||||
{
|
||||
testRunner.ClickByName("Projection mode button");
|
||||
Assert.IsTrue(!view3D.TrackballTumbleWidget.PerspectiveMode);
|
||||
testRunner.Delay(1);
|
||||
Assert.IsTrue(!view3D.TrackballTumbleWidget.PerspectiveMode);
|
||||
}
|
||||
else
|
||||
Assert.IsTrue(view3D.TrackballTumbleWidget.PerspectiveMode);
|
||||
|
||||
Vector3[] lookAtDirFwds = new Vector3[] {
|
||||
new Vector3(0, 0, -1),
|
||||
new Vector3(0, 0, 1),
|
||||
new Vector3(0, 1, 0),
|
||||
new Vector3(1, 1, 0),
|
||||
new Vector3(-1, -1, 0),
|
||||
new Vector3(0, 1, 1),
|
||||
new Vector3(1, 1, 1),
|
||||
new Vector3(0, 1, -1),
|
||||
new Vector3(1, 1, -1),
|
||||
};
|
||||
|
||||
const int topI = 0;
|
||||
const int bottomI = 1;
|
||||
|
||||
for (int i = 0; i < lookAtDirFwds.Length; ++i)
|
||||
{
|
||||
Vector3 lookAtDirFwd = lookAtDirFwds[i];
|
||||
Vector3 lookAtDirRight = (i == topI ? -Vector3.UnitY : i == bottomI ? Vector3.UnitY : -Vector3.UnitZ).Cross(lookAtDirFwd);
|
||||
Vector3 lookAtDirUp = lookAtDirRight.Cross(lookAtDirFwd).GetNormal();
|
||||
|
||||
var look = Matrix4X4.LookAt(Vector3.Zero, lookAtDirFwd, lookAtDirUp);
|
||||
|
||||
view3D.TrackballTumbleWidget.AnimateRotation(look);
|
||||
testRunner.Delay(0.5);
|
||||
|
||||
testRunner.ClickByName("Zoom to selection button");
|
||||
testRunner.Delay(0.5);
|
||||
|
||||
var part = testRunner.GetObjectByName(wideObject ? CoinName : "Phil A Ment.stl", out _) as IObject3D;
|
||||
AxisAlignedBoundingBox worldspaceAABB = part.GetAxisAlignedBoundingBox();
|
||||
|
||||
Vector2 viewportSize = new Vector2(view3D.TrackballTumbleWidget.Width, view3D.TrackballTumbleWidget.Height);
|
||||
RectangleDouble rect = view3D.TrackballTumbleWidget.WorldspaceAabbToBottomScreenspaceRectangle(worldspaceAABB);
|
||||
Vector2 screenspacePositionOfWorldspaceCenter = view3D.TrackballTumbleWidget.WorldspaceToBottomScreenspace(worldspaceAABB.Center).Xy;
|
||||
double marginPixels = CameraFittingUtil.MarginScale * Math.Min(viewportSize.X, viewportSize.Y);
|
||||
|
||||
const double pixelTolerance = 1e-3;
|
||||
|
||||
// Check that the full object is visible.
|
||||
Assert.IsTrue(rect.Left > -pixelTolerance);
|
||||
Assert.IsTrue(rect.Bottom > -pixelTolerance);
|
||||
Assert.IsTrue(rect.Right < viewportSize.X + pixelTolerance);
|
||||
Assert.IsTrue(rect.Top < viewportSize.Y + pixelTolerance);
|
||||
|
||||
// Check for centering.
|
||||
|
||||
bool isPerspectiveFittingWithinMargin =
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.Sphere ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.CenterOnWorldspaceAABB ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.CenterOnViewspaceAABB ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithApproxCentering ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithPerfectCentering;
|
||||
|
||||
// Tightly bounded. At least one axis should be bounded by the margin.
|
||||
bool isPerspectiveFittingBoundedByMargin =
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithApproxCentering ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithPerfectCentering;
|
||||
|
||||
bool perspectiveFittingWillCenterTheAABBCenter =
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.Sphere ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.CenterOnWorldspaceAABB ||
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.CenterOnViewspaceAABB;
|
||||
|
||||
bool perspectiveFittingWillCenterTheScreenspaceAABB =
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm == CameraFittingUtil.EPerspectiveFittingAlgorithm.IntersectionOfBoundingPlanesWithPerfectCentering;
|
||||
|
||||
// Always get the same result.
|
||||
bool isPerspectiveFittingStable =
|
||||
CameraFittingUtil.PerspectiveFittingAlgorithm != CameraFittingUtil.EPerspectiveFittingAlgorithm.TrialAndError;
|
||||
|
||||
bool isXWorldspaceCentered = MathHelper.AlmostEqual(viewportSize.X / 2, screenspacePositionOfWorldspaceCenter.X, pixelTolerance);
|
||||
bool isYWorldspaceCentered = MathHelper.AlmostEqual(viewportSize.Y / 2, screenspacePositionOfWorldspaceCenter.Y, pixelTolerance);
|
||||
|
||||
bool isXMarginBounded = MathHelper.AlmostEqual(rect.Left, marginPixels, 1e-3) && MathHelper.AlmostEqual(rect.Right, viewportSize.X - marginPixels, pixelTolerance);
|
||||
bool isYMarginBounded = MathHelper.AlmostEqual(rect.Bottom, marginPixels, 1e-3) && MathHelper.AlmostEqual(rect.Top, viewportSize.Y - marginPixels, pixelTolerance);
|
||||
|
||||
bool isXWithinMargin = rect.Left > marginPixels - 1 && rect.Right < viewportSize.X - (marginPixels - 1);
|
||||
bool isYWithinMargin = rect.Bottom > marginPixels - 1 && rect.Top < viewportSize.Y - (marginPixels - 1);
|
||||
|
||||
bool isXScreenspaceCentered = MathHelper.AlmostEqual(viewportSize.X / 2, (rect.Left + rect.Right) / 2, pixelTolerance);
|
||||
bool isYScreenspaceCentered = MathHelper.AlmostEqual(viewportSize.Y / 2, (rect.Bottom + rect.Top) / 2, pixelTolerance);
|
||||
|
||||
if (ortho)
|
||||
{
|
||||
// Ortho fitting will always center the screenspace AABB and the center of the object AABB.
|
||||
Assert.IsTrue(isXWorldspaceCentered && isYWorldspaceCentered);
|
||||
Assert.IsTrue(isXMarginBounded || isYMarginBounded);
|
||||
Assert.IsTrue(isXWithinMargin && isYWithinMargin);
|
||||
Assert.IsTrue(isXScreenspaceCentered && isYScreenspaceCentered);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isPerspectiveFittingWithinMargin)
|
||||
Assert.IsTrue(isXWithinMargin && isYWithinMargin);
|
||||
|
||||
if (isPerspectiveFittingBoundedByMargin)
|
||||
Assert.IsTrue(isXMarginBounded || isYMarginBounded);
|
||||
|
||||
if (perspectiveFittingWillCenterTheAABBCenter)
|
||||
Assert.IsTrue(isXWorldspaceCentered && isYWorldspaceCentered);
|
||||
|
||||
if (perspectiveFittingWillCenterTheScreenspaceAABB)
|
||||
Assert.IsTrue(isXScreenspaceCentered && isYScreenspaceCentered);
|
||||
}
|
||||
|
||||
if (ortho || isPerspectiveFittingStable)
|
||||
{
|
||||
testRunner.ClickByName("Zoom to selection button");
|
||||
testRunner.Delay(1);
|
||||
|
||||
RectangleDouble rect2 = view3D.TrackballTumbleWidget.WorldspaceAabbToBottomScreenspaceRectangle(worldspaceAABB);
|
||||
Assert.IsTrue(rect2.Equals(rect, pixelTolerance));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}, maxTimeToRun: 60 * 3, overrideWidth: 1300, overrideHeight: 800);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public Task OrthographicZoomToSelectionWide()
|
||||
{
|
||||
return DoZoomToSelectionTest(true, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public Task OrthographicZoomToSelectionTall()
|
||||
{
|
||||
return DoZoomToSelectionTest(true, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public Task PerspectiveZoomToSelectionWide()
|
||||
{
|
||||
return DoZoomToSelectionTest(false, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public Task PerspectiveZoomToSelectionTall()
|
||||
{
|
||||
return DoZoomToSelectionTest(false, false);
|
||||
}
|
||||
|
||||
private static void AddCoinToBed(AutomationRunner testRunner, InteractiveScene scene)
|
||||
{
|
||||
testRunner.AddItemToBed(partName: "Row Item MatterControl - Coin.stl")
|
||||
.Delay(.1)
|
||||
.ClickByName(CoinName, offset: new Point2D(-4, 0));
|
||||
Assert.IsNotNull(scene.SelectedItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
<Compile Include="..\MatterControl.Tests\MatterControl\MatterControlUtilities.cs">
|
||||
<Link>MatterControlUtilities.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="CameraFittingUtilTests.cs" />
|
||||
<Compile Include="DesignTools\SheetTests.cs" />
|
||||
<Compile Include="MatterControlTests.cs" />
|
||||
<Compile Include="CreateLibraryFolder.cs" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue