Skip to content

Instantly share code, notes, and snippets.

@dgwaldo
Last active August 29, 2015 14:10
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/b83f1bdfcb0c8b3cb42c to your computer and use it in GitHub Desktop.
Save dgwaldo/b83f1bdfcb0c8b3cb42c to your computer and use it in GitHub Desktop.
WellSurveyPlot3DValuesViewModel extends the WellSurveyPlot3DViewModel
/// <summary>
/// Extends the WellSurveyPlot3DViewModel to provide methods and properties for
/// viewing values along the path of a pipe, internal to the well-bore.
/// </summary>
public class WellSurveyPlot3DValuesViewModel : WellSurveyPlot3DViewModel
{
private double _maxValueOnPipe;
private double _minValueOnPipe;
public WellSurveyPlot3DValuesViewModel(List<WellXyzPoint> globalXyz)
: base(globalXyz)
{
Pipe = new Tube3DGeometryViewModel();
PipeLegend = new RangeColorAxisViewModel();
Well.Fill = new SolidColorBrush(Colors.LightGray) { Opacity = .1 };
Pipe.Fill = Brushes.DarkGray;
}
/// <summary> Parameterless constructor for Blend. </summary>
public WellSurveyPlot3DValuesViewModel() { }
public Tube3DGeometryViewModel Pipe { get; set; }
public RangeColorAxisViewModel PipeLegend { get; set; }
/// <summary>
/// Creates a pipe that follows the well path, and paints the chosen color gradient as the pipes material.
/// </summary>
/// <param name="runDepth"> Run depth in (in). </param>
/// <param name="valuesAtDepthAlongPipe"> Values are normalized and used as texture coordinates. </param>
/// <param name="fill"> A linear gradient brush to apply to the pipe. </param>
public void CreatePipeIn3DWell(double runDepth, Dictionary<double, double> valuesAtDepthAlongPipe, LinearGradientBrush fill)
{
if (Well.Path == null || Well.Path.Count < 1) throw new InvalidOperationException("Well must not be null and must have points.");
Pipe.Path = CalculateGlobalCoordinates(runDepth);
fill.StartPoint = new Point(0, 0); //Ensure the fill is horizontal for pipe.
fill.EndPoint = new Point(1, 0);
fill.Opacity = 1;
Pipe.Fill = fill;
Pipe.TextureCoordinates = NormalizeValues(InterpolatedValues(runDepth, valuesAtDepthAlongPipe));
Pipe.Diameter = Well.Diameter - 10;
OnPropertyChanged("Pipe");
}
/// <summary> Sets up the legend for showing the values along the pipe.
/// Sets the value step to be 10 equal intervals between the max and min TextureCoordinate values. </summary>
/// <param name="legendTitle">Title to display above legend. </param>
/// <param name="legenedBrush">Brush direction for legend should be vertical. </param>
/// <param name="format"></param>
public void SetupPipeLegend(string legendTitle, LinearGradientBrush legenedBrush, string format = "")
{
if (Pipe.TextureCoordinates == null || Pipe.TextureCoordinates.Count < 1) throw new InvalidOperationException("Pipe texture coordinates must not be null and must have points.");
PipeLegend.LegendTitle = legendTitle;
PipeLegend.ColorScheme = legenedBrush;
PipeLegend.Step = (_maxValueOnPipe - _minValueOnPipe) / 10;
PipeLegend.Min = _minValueOnPipe;
PipeLegend.Max = _maxValueOnPipe;
PipeLegend.MinTextureCoordinate = Pipe.TextureCoordinates.Min();
PipeLegend.MaxTextureCoordinate = Pipe.TextureCoordinates.Max();
PipeLegend.AxisValueFormatString = format;
OnPropertyChanged("PipeLegend");
}
/// <summary>
/// Interpolates the incoming values at each 3D point along the pipe path.
/// </summary>
/// <param name="runDepth"></param>
/// <param name="valuesAtDepthAlongPipe"></param>
/// <returns></returns>
private List<double> InterpolatedValues(double runDepth, Dictionary<double, double> valuesAtDepthAlongPipe)
{
var interpolatedValues = new List<double>();
var keys = new List<double>(valuesAtDepthAlongPipe.Keys);
var step = runDepth / Pipe.Path.Count - 1;
for (var i = 0; i < Pipe.Path.Count; i++)
{
var depthIndx = Math.Abs(keys.BinarySearch(i * step));
if (depthIndx >= keys.Count - 1) depthIndx -= 1;
var nearestDepth = keys[depthIndx];
var nearestPrvDepth = (i > 0) ? keys[depthIndx - 1] : keys[depthIndx];
var depth = (nearestDepth + nearestPrvDepth) / 2;
var val = valuesAtDepthAlongPipe[nearestDepth];
var preVal = valuesAtDepthAlongPipe[nearestPrvDepth];
interpolatedValues.Add(Interpolate(preVal, val, nearestPrvDepth, depth, nearestDepth));
}
return interpolatedValues;
}
/// <summary>
/// Interpolates or extrapolates to find the unknown X.
/// X1,Y1
/// Solve for X2 Y2
/// X3,Y3
/// </summary>
/// <param name="x1"></param>
/// <param name="y1"></param>
/// <param name="y2">Y-Value for which the function will return X.</param>
/// <param name="x3"></param>
/// <param name="y3"></param>
/// <remarks>http://www.ajdesigner.com/phpinterpolation/linear_interpolation_equation.php</remarks>
private double Interpolate(double x1, double x3, double y1, double y2, double y3)
{
if (Math.Abs(y3 - y1 - 0) < .0001) return x1;
return x1 + (y2 - y1) * (x3 - x1) / (y3 - y1);
}
/// <summary>
/// Normalize values between 0 & 1 to match scaling of linear gradient color brush.
/// </summary>
/// <param name="valuesAtDepthAlongPipe"></param>
/// <returns></returns>
private DoubleCollection NormalizeValues(List<double> valuesAtDepthAlongPipe)
{
_maxValueOnPipe = valuesAtDepthAlongPipe.Max();
_minValueOnPipe = valuesAtDepthAlongPipe.Min();
return new DoubleCollection(valuesAtDepthAlongPipe.Select(d => (d - _minValueOnPipe) / (_maxValueOnPipe - _minValueOnPipe)));
}
}
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=".2*" />
<RowDefinition Height="*"/>
<RowDefinition Height=".7*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".2*"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width=".2*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" ZIndex="100" DockPanel.Dock="Left">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding PipeLegend.LegendTitle}" />
<h:RangeColorAxis Grid.Row="1"
FormatString="{Binding PipeLegend.AxisValueFormatString}"
Minimum="{Binding PipeLegend.Min}"
Maximum="{Binding PipeLegend.Max}"
MaximumTextureCoordinate="{Binding PipeLegend.MaxTextureCoordinate}"
MinimumTextureCoordinate="{Binding PipeLegend.MinTextureCoordinate}"
Step="{Binding PipeLegend.Step}" Margin="6"
Position="Left"
ColorScheme="{Binding PipeLegend.ColorScheme}"
FlipColorScheme="{Binding PipeLegend.FlipColorScheme}" />
</Grid>
<DockPanel Grid.Row="0" Grid.RowSpan="3" Grid.ColumnSpan="3" Grid.Column="0" Background="White">
<Menu DockPanel.Dock="Top">
<!-Omitted for brevity-->
</Menu>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Bottom" Opacity="0.5" >
<StackPanel>
<TextBlock Text="Well Diameter" />
<Slider x:Name="WellDiamSlider" Value="{Binding Well.Diameter}" ToolTip="{Binding ElementName=WellDiamSlider, Path=Value}" Minimum="10" Maximum="500" Width="100" Margin="10"/>
</StackPanel>
<StackPanel>
<TextBlock Text="Pipe Diameter" />
<Slider x:Name="PipeDiamSlider" Value="{Binding Pipe.Diameter}" ToolTip="{Binding ElementName=PipeDiamSlider, Path=Value}" Minimum="10" Maximum="500" Width="100" Margin="10"/>
</StackPanel>
<StackPanel>
<TextBlock Text="Grid-Line Thickness" />
<Slider x:Name="GridSlider" Value="{Binding GridLineWidth}"
ToolTip="{Binding ElementName=GridSlider, Path=Value}" Minimum="1" Maximum="50" Width="100" Margin="10"/>
</StackPanel>
<StackPanel>
<CheckBox x:Name="PipeIsVisibleCheck" IsChecked="True" Content="Render Pipe" />
<CheckBox x:Name="WellIsVisibleCheck" IsChecked="True" Content="Render Well" />
<CheckBox x:Name="RenderGridCheckBox" IsChecked="True" Content="Render Grid " />
<CheckBox x:Name="FreezeAxisLblsCheckBox" IsChecked="True" Content="Freeze Axis Labels" />
</StackPanel>
</StackPanel>
<h:HelixViewport3D x:Name="HelixViewport3D" ZoomExtentsWhenLoaded="True" ShowCoordinateSystem="True"
CameraRotationMode="{Binding ElementName=RotationMode, Path=SelectedItem}"
DefaultCamera="{Binding PerspectiveCamera, UpdateSourceTrigger=PropertyChanged}"
CameraMode="{Binding ElementName=CameraMode, Path= SelectedItem}"
PanGesture="LeftClick" >
<h:DefaultLights />
<h:TubeVisual3D x:Name="PipeVisual" Path="{Binding Pipe.Path}"
TextureCoordinates="{Binding Pipe.TextureCoordinates}"
Diameter="{Binding ElementName=PipeDiamSlider, Path= Value }"
Material="{Binding Pipe.Material}"
BackMaterial="{Binding Pipe.Material}"
ThetaDiv="50" IsPathClosed="False"
Visible="{Binding ElementName=PipeIsVisibleCheck, Path=IsChecked}"/>
<h:TubeVisual3D x:Name="WellVisual" Path="{Binding Well.Path}"
TextureCoordinates="{Binding Well.TextureCoordinates}"
Diameter="{Binding ElementName=WellDiamSlider,Path= Value}"
Material="{Binding Well.Material}"
BackMaterial="{Binding Well.Material}"
ThetaDiv="50" IsPathClosed="False"
Visible="{Binding ElementName=WellIsVisibleCheck, Path=IsChecked}"/>
<h:GridLinesVisual3D Width="{Binding GridLength}" Length="{Binding GridLength}" MajorDistance="{Binding MajorGridSpacing}"
Visible="{Binding IsChecked, ElementName=RenderGridCheckBox}" Thickness="{Binding ElementName=GridSlider, Path=Value }"
MinorDistance="{Binding MinorGridSpacing}" LengthDirection="1,0,0" Normal="0,0,1"
Center="{Binding BottomPlaneCenter,UpdateSourceTrigger=PropertyChanged}" Fill="{Binding SelectedColorText, ElementName=BackPlaneColor}" />
<h:GridLinesVisual3D Width="{Binding GridLength}" Length="{Binding GridLength}" LengthDirection="0,0,1" Normal="1,0,0"
Visible="{Binding IsChecked, ElementName=RenderGridCheckBox}" Thickness="{Binding ElementName=GridSlider, Path=Value }"
MajorDistance="{Binding MajorGridSpacing}" MinorDistance="{Binding MinorGridSpacing}"
Center="{Binding BackLeftPlaneCenter, UpdateSourceTrigger=PropertyChanged}" Fill="{Binding SelectedColorText, ElementName=BackPlaneColor}" />
<h:GridLinesVisual3D Width="{Binding GridLength}" Length="{Binding GridLength}" LengthDirection="1,0,0" Normal="0,1,0"
Visible="{Binding IsChecked, ElementName=RenderGridCheckBox}" Thickness="{Binding ElementName=GridSlider, Path=Value }"
MajorDistance="{Binding MajorGridSpacing}" MinorDistance="{Binding MinorGridSpacing}"
Center="{Binding BackRightPlaneCenter,UpdateSourceTrigger=PropertyChanged}" Fill="{Binding SelectedColorText, ElementName=BackPlaneColor}" />
<h:BillboardTextGroupVisual3D Background="Gray" Foreground="White" FontSize="12" Offset="2,2"
Padding="1" Items="{Binding GridLabels, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
IsEnabled="{Binding IsChecked, ElementName=FreezeAxisLblsCheckBox}" />
<h:BillboardTextGroupVisual3D Background="Black" Foreground="White" FontSize="10" Offset="3,3"
Padding="2" Items="{Binding MarkerLabels, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
</h:HelixViewport3D>
</DockPanel>
</Grid>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment