Skip to content

Instantly share code, notes, and snippets.

@dgwaldo
Last active August 29, 2015 14:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dgwaldo/066e448c6e5447421ca4 to your computer and use it in GitHub Desktop.
Save dgwaldo/066e448c6e5447421ca4 to your computer and use it in GitHub Desktop.
public class WellSurveyPlot3DViewModel : BindableBase
{
private readonly List<WellXyzPoint> _calculatedSurveyPoints;
private int _xDir;
private int _yDir;
public WellSurveyPlot3DViewModel(List<WellXyzPoint> calculatedSurveyPoints)
{
_calculatedSurveyPoints = calculatedSurveyPoints;
MajorGridSpacing = 300; //Meters
MinorGridSpacing = 150; //Meters
MarkerLabels = new List<BillboardTextItem>();
Well = new Tube3DGeometryViewModel();
}
/// <summary> Constructor for Blend. </summary>
public WellSurveyPlot3DViewModel() { }
public int MajorGridSpacing { get; set; }
public int MinorGridSpacing { get; set; }
public int GridLength { get; set; }
public int GridLineWidth { get; set; }
public int Xdir { get { return _xDir; } }
public int Ydir { get { return _yDir; } }
public Point3D BottomPlaneCenter { get; private set; }
public Point3D BackLeftPlaneCenter { get; private set; }
public Point3D BackRightPlaneCenter { get; private set; }
public List<BillboardTextItem> GridLabels { get; private set; }
public List<BillboardTextItem> MarkerLabels { get; private set; }
public PerspectiveCamera PerspectiveCamera { get; private set; }
public Tube3DGeometryViewModel Well { get; set; }
public void CreateWell3DPlot()
{
Well.Path = CalculateGlobalCoordinates(_calculatedSurveyPoints.Last().MeasuredDepth);
SetGridSpacingAccordingToUnits();
SetGridLength();
SetSceneSizesAccordingToGridSize();
SetXyAxisDirections(Well.Path.Last().X, Well.Path.Last().Y);
CalculateBackPlaneCenterPoints();
SetupPerspectiveCamera();
CreateAxisLables();
OnPropertyChanged("");
}
/// <summary>
/// Well must be plotted before calling this method. This will add a marker at the nearest 3D point in the well-bore.
/// </summary>
/// <param name="depth"></param>
/// <param name="text"></param>
public void AddWellBoreMarker(double depth, string text)
{
if (Well.Path == null) throw new InvalidOperationException("Well path must contain points before a marker can be added.");
var idxPntAlongPath = _calculatedSurveyPoints.FindIndex(pnt => pnt.MeasuredDepth > depth);
MarkerLabels.Add(new BillboardTextItem { Text = text, Position = Well.Path[idxPntAlongPath] });
OnPropertyChanged("MarkerLabels");
}
/// <summary>
/// Creates a set of 3D points for the given global coordinates.
/// </summary>
/// <param name="depth">Depth given in users current units. </param>
/// <returns></returns>
protected Point3DCollection CalculateGlobalCoordinates(double depth)
{
return new Point3DCollection(_calculatedSurveyPoints
.Where(x => x.MeasuredDepth <= depth)
.Select(pnt => new Point3D
{
X = UnitsContext.Create.LengthLongUnit(-1 * pnt.Y).UserValue,
Y = UnitsContext.Create.LengthLongUnit(-1 * pnt.Z).UserValue,
Z = UnitsContext.Create.LengthLongUnit(-pnt.X).UserValue
}));
}
private void SetGridSpacingAccordingToUnits()
{
if (UnitsContext.Current.LengthLong == LengthLongSymbols.Ft)
{
MajorGridSpacing = 1000;
MinorGridSpacing = 500;
}
}
private void SetGridLength()
{
var maxX = Well.Path.Min(x => x.X);
var maxY = Well.Path.Min(x => x.Y);
var minZ = Well.Path.Min(x => x.Z);
var maxAxis = new[] { Math.Abs(maxX), Math.Abs(maxY), Math.Abs(minZ) }.Max();
var maxAxisToNearestMajorInterval = (int)RoundToNearest(maxAxis, MajorGridSpacing);
if (maxAxisToNearestMajorInterval % 2 != 0)
maxAxisToNearestMajorInterval += MajorGridSpacing; //Ensure odd number so we have true half point
GridLength = maxAxisToNearestMajorInterval;
}
/// <summary> Keeps the tube and grid proportionate no matter the zoom. </summary>
private void SetSceneSizesAccordingToGridSize()
{
const double gridScaleFactor = .001;
const double tubeScaleFactor = .01;
Well.Diameter = (int)(GridLength * tubeScaleFactor);
GridLineWidth = (int)(GridLength * gridScaleFactor);
}
private void CalculateBackPlaneCenterPoints()
{
// Logic for grid coordinates is odd, not the typical x,y directions
// +x-y | -x-y
// |
// ------ ------
// +x+y | -x+y
// |
//If xDir and yDir and unmodified, Backplanes for 0 to 90
var centerPoint = GridLength / 2;
BackLeftPlaneCenter = new Point3D(0, _yDir * centerPoint, -centerPoint);
BackRightPlaneCenter = new Point3D(_xDir * centerPoint, 0, -centerPoint);
BottomPlaneCenter = new Point3D(_xDir * centerPoint, _yDir * centerPoint, -GridLength);
}
private void SetupPerspectiveCamera()
{
PerspectiveCamera = new PerspectiveCamera
{
//Position: will be set when ZoomExtentsWhenLoaded is set to True in Xaml.
//Position = new Point3D(_xDir * GridLength, _yDir * GridLength, .5 * -GridLength),
LookDirection = new Vector3D(-1 * _xDir * GridLength, -1 * _yDir * GridLength, .5 * -GridLength),
UpDirection = new Vector3D(0, 0, 1),
FieldOfView = 20.0,
};
}
private void SetXyAxisDirections(double lastX, double lastY)
{
_xDir = -1;
_yDir = -1;
if (lastX < 0 & lastY > 0) //90 to 180
_yDir = 1;
if (lastX > 0 & lastY > 0) //180 to 270
{ _yDir = 1; _xDir = 1; }
if (lastX > 0 & lastY < 0) //270 to 0
_xDir = 1;
}
private void CreateAxisLables()
{
GridLabels = new List<BillboardTextItem>();
for (var i = 0; i <= GridLength; i += MajorGridSpacing * 4) //Reduce number of labels by stepping
{
GridLabels.Add(new BillboardTextItem
{
Text = String.Format("{0} {1}", i.ToString("N0"), UnitsContext.CurrentSymbols.LengthLong),
Position = new Point3D(_xDir * i, _yDir * GridLength + 100, -GridLength),
WorldDepthOffset = 100
});
GridLabels.Add(new BillboardTextItem
{
Text = String.Format("{0} {1}", i.ToString("N0"), UnitsContext.CurrentSymbols.LengthLong),
Position = new Point3D(_xDir * GridLength + 100, 0, -i),
WorldDepthOffset = 100
});
}
}
protected static double RoundToNearest(double amount, double roundTo)
{
double remainder = amount % roundTo;
if (remainder < (roundTo / 2))
{
amount -= remainder;
}
else
{
amount += (roundTo - remainder);
}
return amount;
}
}
public class Tube3DGeometryViewModel
{
/// <summary> Tube diameter in coordinates relative to XYZ path. </summary>
public double Diameter { get; set; }
/// <summary> Number of sections around the circle. </summary>
public double ThetaDiv { get; set; }
/// <summary> XYZ Point collection. </summary>
public Point3DCollection Path { get; set; }
/// <summary> Brush used for fill, if set will also be used for the material. </summary>
public Brush Fill { get; set; }
/// <summary> Texture coordinates, define the value to show from a color gradient, at each XYZ point, normalized between 0 and 1. </summary>
public DoubleCollection TextureCoordinates { get; set; }
/// <summary> Material, uses the fill brush to create a material, can be used in place of fill. </summary>
public Material Material
{
get { return MaterialHelper.CreateMaterial(Fill); }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment