using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows;
namespace HelixToolkit
{
///
/// Terrain model - reading model from .bt files
/// The origin of model will be centered around the midpoint of the model.
/// Also support a ".btz" format - compressed with gzip. A compression method to convert from ".bt" to ".btz" can be found in the GZipHelper.
/// No advanced LOD algorithm imported - this is for small terrains only...
///
public class TerrainVisual3D : ModelVisual3D
{
public string Source
{
get { return (string)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register("Source", typeof(string), typeof(TerrainVisual3D), new UIPropertyMetadata(null, SourceChanged));
protected static void SourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
((TerrainVisual3D)obj).UpdateModel();
}
public TerrainModel Model
{
get { return (TerrainModel)GetValue(ModelProperty); }
set { SetValue(ModelProperty, value); }
}
public static readonly DependencyProperty ModelProperty = DependencyProperty.Register("Model", typeof(TerrainModel), typeof(TerrainVisual3D), new UIPropertyMetadata(null, ModelChanged));
protected static void ModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
((TerrainVisual3D)obj).DrawModel();
}
private readonly ModelVisual3D visualChild;
public TerrainVisual3D()
{
visualChild = new ModelVisual3D();
Children.Add(visualChild);
}
void UpdateModel()
{
var r = new TerrainModel();
r.Load(Source);
//r.Texture = new SlopeDirectionTexture(0);
r.Texture = new SlopeTexture(8);
// r.Texture = new MapTexture(@"D:\tmp\CraterLake.png") { Left = r.Left, Right = r.Right, Top = r.Top, Bottom = r.Bottom };
visualChild.Content = r.CreateModel(2);
}
void DrawModel()
{
Model.Texture = new SlopeTexture(8);
visualChild.Content = Model.CreateModel(2);
}
}
public class TerrainTexture
{
public Material Material { get; set; }
public PointCollection TextureCoordinates { get; set; }
public TerrainTexture()
{
Material = Materials.Green;
}
public virtual void Calculate(TerrainModel model, MeshGeometry3D mesh)
{
}
}
///
/// Terrain texture using a bitmap. Set the Left,Right,Bottom and Top coordinates to get the right alignment.
///
public class MapTexture : TerrainTexture
{
public double Left { get; set; }
public double Right { get; set; }
public double Bottom { get; set; }
public double Top { get; set; }
public MapTexture(string source)
{
Material = MaterialHelper.CreateImageMaterial(source,1);
}
public override void Calculate(TerrainModel model, MeshGeometry3D mesh)
{
var texcoords = new PointCollection();
foreach (var p in mesh.Positions)
{
double x = p.X + model.Offset.X;
double y = p.Y + model.Offset.Y;
double u = (x - Left) / (Right - Left);
double v = (y - Top) / (Bottom - Top);
texcoords.Add(new Point(u, v));
}
TextureCoordinates = texcoords;
}
}
///
/// Texture by the slope angle.
///
public class SlopeTexture : TerrainTexture
{
public Brush Brush { get; set; }
public SlopeTexture(int gradientSteps)
{
if (gradientSteps > 0)
Brush = BrushHelper.CreateSteppedGradientBrush(GradientBrushes.BlueWhiteRed, gradientSteps);
else
Brush = GradientBrushes.BlueWhiteRed;
}
public override void Calculate(TerrainModel model, MeshGeometry3D mesh)
{
var normals = MeshGeometryHelper.CalculateNormals(mesh);
var texcoords = new PointCollection();
var up = new Vector3D(0, 0, 1);
for (int i = 0; i < normals.Count; i++)
{
double slope = Math.Acos(Vector3D.DotProduct(normals[i], up)) * 180 / Math.PI;
double u = slope / 40;
if (u > 1) u = 1;
if (u < 0) u = 0;
texcoords.Add(new Point(u, u));
}
TextureCoordinates = texcoords;
Material = MaterialHelper.CreateMaterial(Brush);
}
}
///
/// Texture by the direction of the steepest gradient.
///
public class SlopeDirectionTexture : TerrainTexture
{
public Brush Brush { get; set; }
public SlopeDirectionTexture(int gradientSteps)
{
if (gradientSteps > 0)
Brush = BrushHelper.CreateSteppedGradientBrush(GradientBrushes.Hue, gradientSteps);
else
Brush = GradientBrushes.Hue;
}
public override void Calculate(TerrainModel model, MeshGeometry3D mesh)
{
var normals = MeshGeometryHelper.CalculateNormals(mesh);
var texcoords = new PointCollection();
for (int i = 0; i < normals.Count; i++)
{
double slopedir = Math.Atan2(normals[i].Y, normals[i].X) * 180 / Math.PI;
if (slopedir < 0) slopedir += 360;
double u = slopedir / 360;
texcoords.Add(new Point(u, u));
}
TextureCoordinates = texcoords;
Material = MaterialHelper.CreateMaterial(Brush);
}
}
///
/// Read .bt files from disk, keeps the model data and creates the Model3D.
/// The .btz format is a gzip compressed version of the .bt format.
///
public class TerrainModel
{
public double Left { get; set; }
public double Right { get; set; }
public double Bottom { get; set; }
public double Top { get; set; }
public double MinimumZ { get; set; }
public double MaximumZ { get; set; }
public double[] Data { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public Point3D Offset { get; set; }
public TerrainTexture Texture { get; set; }
public void Load(string source)
{
var ext = Path.GetExtension(source).ToLower();
switch (ext)
{
case ".btz": ReadBTZ(source);
break;
case ".bt": ReadBT(source);
break;
}
}
private void ReadBTZ(string source)
{
var infile = File.OpenRead(source);
var deflateStream = new GZipStream(infile, CompressionMode.Decompress, true);
ReadBT(deflateStream);
deflateStream.Close();
infile.Close();
}
private void ReadBT(string source)
{
var infile = File.OpenRead(source);
ReadBT(infile);
infile.Close();
}
///
/// Creates the 3D model of the terrain.
///
/// The level of detail.
///
public GeometryModel3D CreateModel(int lod)
{
int ni = Height / lod;
int nj = Width / lod;
var pts = new Point3DCollection(ni * nj);
double mx = (Left + Right) / 2;
double my = (Top + Bottom) / 2;
double mz = (MinimumZ + MaximumZ) / 2;
Offset = new Point3D(mx, my, mz);
for (int i = 0; i < ni; i++)
for (int j = 0; j < nj; j++)
{
double x = Left + (Right - Left) * j / (nj - 1);
double y = Top + (Bottom - Top) * i / (ni - 1);
double z = Data[i * lod * Width + j * lod];
x -= Offset.X;
y -= Offset.Y;
z -= Offset.Z;
pts.Add(new Point3D(x, y, z));
}
var mb = new MeshBuilder(false, false);
mb.AddRectangularMesh(pts, nj);
var mesh = mb.ToMesh();
var material = Materials.Green;
if (Texture != null)
{
Texture.Calculate(this, mesh);
material = Texture.Material;
mesh.TextureCoordinates = Texture.TextureCoordinates;
}
var model = new GeometryModel3D();
model.Geometry = mesh;
model.Material = material;
model.BackMaterial = material;
return model;
}
///
/// Reads a .bt (Binary terrain) file.
/// http://www.vterrain.org/Implementation/Formats/BT.html
///
/// The stream.
///
public void ReadBT(Stream s)
{
var reader = new BinaryReader(s);
var buffer = reader.ReadBytes(10);
var enc = new ASCIIEncoding();
var marker = enc.GetString(buffer);
if (!marker.StartsWith("binterr"))
throw new FileFormatException("Invalid marker.");
var version = marker.Substring(7);
Width = reader.ReadInt32();
Height = reader.ReadInt32();
short dataSize = reader.ReadInt16();
bool isFloatingPoint = reader.ReadInt16() == 1;
short horizontalUnits = reader.ReadInt16();
short utmZone = reader.ReadInt16();
short datum = reader.ReadInt16();
Left = reader.ReadDouble();
Right = reader.ReadDouble();
Bottom = reader.ReadDouble();
Top = reader.ReadDouble();
short proj = reader.ReadInt16();
float scale = reader.ReadSingle();
var padding = reader.ReadBytes(190);
int index = 0;
Data = new double[Width * Height];
MinimumZ = double.MaxValue;
MaximumZ = double.MinValue;
for (int y = 0; y < Height; y++)
for (int x = 0; x < Width; x++)
{
double z;
if (dataSize == 2)
{
z = reader.ReadUInt16();
}
else
{
z = isFloatingPoint ? reader.ReadSingle() : reader.ReadUInt32();
}
Data[index++] = z;
if (z < MinimumZ) MinimumZ = z;
if (z > MaximumZ) MaximumZ = z;
}
reader.Close();
}
}
}